├── .editorconfig ├── .gitattributes ├── .github ├── test-scala-presence.toml └── workflows │ ├── build-and-test.yml │ ├── release-tags.yml │ └── test-scala-presence.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── build.gradle ├── dependencies.gradle ├── example_website ├── .htaccess ├── favicon.ico ├── index.php ├── login.css ├── login.html └── style.css ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── repositories.gradle ├── settings.gradle └── src └── main ├── java └── pl │ └── kuba6000 │ └── ae2webintegration │ ├── ae2interface │ ├── AE2WebIntegration.java │ ├── CraftingMediumTracker.java │ ├── implementations │ │ └── AE.java │ └── mixins │ │ ├── AE2 │ │ ├── CraftingCPUClusterMixin.java │ │ ├── CraftingGridCacheMixin.java │ │ └── implementations │ │ │ ├── AECraftingCPUClusterMixin.java │ │ │ ├── AECraftingJobMixin.java │ │ │ ├── AECraftingPatternDetailsMixin.java │ │ │ ├── AEGridMixin.java │ │ │ ├── AEItemListMixin.java │ │ │ ├── AEItemStackMixin.java │ │ │ ├── AEMeInventoryItemMixin.java │ │ │ ├── AEPlayerDataMixin.java │ │ │ ├── PatternProviderViewableMixin.java │ │ │ └── service │ │ │ ├── AECraftingGridMixin.java │ │ │ ├── AEPathingGridMixin.java │ │ │ ├── AESecurityGridMixin.java │ │ │ └── AEStorageGridMixin.java │ │ └── MixinPlugin.java │ └── core │ ├── AE2Controller.java │ ├── AE2JobTracker.java │ ├── AE2WebIntegration.java │ ├── AEMixinCallbacks.java │ ├── AEWebAPI.java │ ├── ClientProxy.java │ ├── CommonProxy.java │ ├── Config.java │ ├── FMLEventHandler.java │ ├── GridData.java │ ├── PasswordHelper.java │ ├── WebData.java │ ├── ae2request │ ├── IRequest.java │ ├── async │ │ ├── GetTracking.java │ │ ├── GetTrackingHistory.java │ │ ├── GridSettings.java │ │ └── IAsyncRequest.java │ └── sync │ │ ├── CancelCPU.java │ │ ├── GetCPU.java │ │ ├── GetCPUList.java │ │ ├── GetGridList.java │ │ ├── GetItems.java │ │ ├── ISyncedRequest.java │ │ ├── Job.java │ │ └── Order.java │ ├── api │ ├── AEApi │ │ ├── AEActionable.java │ │ └── AEControllerState.java │ ├── DimensionalCoords.java │ ├── IAEMixinCallbacks.java │ ├── IAEWebInterface.java │ ├── JSON_CompactedItem.java │ ├── JSON_CompactedJobTrackingInfo.java │ └── JSON_DetailedItem.java │ ├── commands │ └── BaseCommandHandler.java │ ├── discord │ └── DiscordManager.java │ ├── interfaces │ ├── IAE.java │ ├── IAECraftingJob.java │ ├── IAECraftingPatternDetails.java │ ├── IAEGrid.java │ ├── IAEMeInventoryItem.java │ ├── IAEPlayerData.java │ ├── ICraftingCPUCluster.java │ ├── IItemList.java │ ├── IItemStack.java │ ├── IPatternProviderViewable.java │ └── service │ │ ├── IAECraftingGrid.java │ │ ├── IAEPathingGrid.java │ │ ├── IAESecurityGrid.java │ │ └── IAEStorageGrid.java │ └── utils │ ├── GSONUtils.java │ ├── HTTPUtils.java │ ├── RateLimiter.java │ └── VersionChecker.java └── resources ├── LICENSE ├── assets ├── favicon.ico ├── login.html └── webpage.html ├── mcmod.info └── mixins.ae2webintegration.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # This is the universal Text Editor Configuration 2 | # for all GTNewHorizons projects 3 | # See: https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 4 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.{bat,ini}] 16 | end_of_line = crlf 17 | 18 | [*.{dtd,json,info,mcmeta,md,sh,svg,xml,xsd,xsl,yaml,yml}] 19 | indent_size = 2 20 | 21 | [*.lang] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.[jJ][aA][rR] binary 4 | 5 | *.[pP][nN][gG] binary 6 | *.[jJ][pP][gG] binary 7 | *.[jJ][pP][eE][gG] binary 8 | *.[gG][iI][fF] binary 9 | *.[tT][iI][fF] binary 10 | *.[tT][iI][fF][fF] binary 11 | *.[iI][cC][oO] binary 12 | *.[sS][vV][gG] text 13 | *.[eE][pP][sS] binary 14 | *.[xX][cC][fF] binary 15 | 16 | *.[kK][aA][rR] binary 17 | *.[mM]4[aA] binary 18 | *.[mM][iI][dD] binary 19 | *.[mM][iI][dD][iI] binary 20 | *.[mM][pP]3 binary 21 | *.[oO][gG][gG] binary 22 | *.[rR][aA] binary 23 | 24 | *.7[zZ] binary 25 | *.[gG][zZ] binary 26 | *.[tT][aA][rR] binary 27 | *.[tT][gG][zZ] binary 28 | *.[zZ][iI][pP] binary 29 | 30 | *.[tT][cC][nN] binary 31 | *.[sS][oO] binary 32 | *.[dD][lL][lL] binary 33 | *.[dD][yY][lL][iI][bB] binary 34 | *.[pP][sS][dD] binary 35 | *.[tT][tT][fF] binary 36 | *.[oO][tT][fF] binary 37 | 38 | *.[pP][aA][tT][cC][hH] -text 39 | 40 | *.[bB][aA][tT] text eol=crlf 41 | *.[cC][mM][dD] text eol=crlf 42 | *.[pP][sS]1 text eol=crlf 43 | 44 | *[aA][uU][tT][oO][gG][eE][nN][eE][rR][aA][tT][eE][dD]* binary 45 | -------------------------------------------------------------------------------- /.github/test-scala-presence.toml: -------------------------------------------------------------------------------- 1 | [exclude] 2 | "src/main/java/**/*.java" = "import scala." -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build and test 3 | 4 | on: 5 | pull_request: 6 | branches: [ master, main ] 7 | push: 8 | branches: [ master, main ] 9 | 10 | jobs: 11 | build-and-test: 12 | uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master 13 | secrets: inherit 14 | with: 15 | timeout: 150 16 | -------------------------------------------------------------------------------- /.github/workflows/release-tags.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Release tagged build 3 | 4 | on: 5 | push: 6 | tags: [ '*' ] 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | release-tags: 13 | uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master 14 | secrets: inherit 15 | -------------------------------------------------------------------------------- /.github/workflows/test-scala-presence.yml: -------------------------------------------------------------------------------- 1 | name: Test Scala Presence 2 | 3 | on: 4 | pull_request: 5 | branches: [ master, main ] 6 | push: 7 | branches: [ master, main ] 8 | 9 | jobs: 10 | test-scala-presence: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Check file content 16 | uses: mattsb42-meta/not-grep@1.0.0 17 | with: 18 | config-file: ./.github/test-scala-presence.toml 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | /.idea/ 4 | /.vscode/ 5 | /run/ 6 | /build/ 7 | /eclipse/ 8 | .classpath 9 | .project 10 | /bin/ 11 | /config/ 12 | /crash-reports/ 13 | /logs/ 14 | options.txt 15 | /saves/ 16 | usernamecache.json 17 | banned-ips.json 18 | banned-players.json 19 | eula.txt 20 | ops.json 21 | server.properties 22 | servers.dat 23 | usercache.json 24 | whitelist.json 25 | /out/ 26 | *.iml 27 | *.ipr 28 | *.iws 29 | src/main/resources/mixins.*([!.]).json 30 | *.bat 31 | *.DS_Store 32 | !gradlew.bat 33 | .factorypath 34 | addon.local.gradle 35 | addon.local.gradle.kts 36 | addon.late.local.gradle 37 | addon.late.local.gradle.kts 38 | layout.json 39 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @kuba6000 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | AE2 Web Integration - Minecraft addon 2 | Copyright (C) 2024 kuba6000 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 3 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public License 15 | along with this library. If not, see . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # AE2 Web Integration 4 | 5 | [![CF Downloads](https://cf.way2muchnoise.eu/full_1122761_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/ae2-web-integration) [![Modrinth Downloads](https://img.shields.io/modrinth/dt/8MGTfmHF?logo=modrinth) 6 | ](https://modrinth.com/mod/ae2-web-integration) 7 | 8 |
9 | 10 | An add-on for AE2 that lets you view your terminal in your web browser wherever you are! This also includes viewing orders, cancelling them and even making a new ones! There is also a discord integration (through a webhook)! 11 | 12 | **Now also works on multiple networks and on public server = all players can have their own account!** 13 | 14 | **THE MOD SHOULD ONLY BE INSTALLED ON SERVER SIDE, THERE IS NO ITEMS ADDED IN THE GAME!** 15 | 16 | # Showcase on youtube 17 | 18 | ### Main mod 19 | [![YT showcase](https://img.youtube.com/vi/3uey6nuW09g/0.jpg)](https://www.youtube.com/watch?v=3uey6nuW09g) 20 | ### Discord integration 21 | [![YT showcase](https://img.youtube.com/vi/e4a86dBz7NY/0.jpg)](https://www.youtube.com/watch?v=e4a86dBz7NY) 22 | 23 | # How? 24 | 25 | This mod starts a web server on server boot that hosts a simple website that you can access directly, or through API calls 26 | 27 | ## How does web panel know which network to chose from? 28 | 29 | Because the mod is server sided only, there is no additional terminal or block added that could identify the networks, 30 | Networks are identified through wireless access points (or security terminal on older mc versions), 31 | all players who wants to access a specific network through the website, must place an wireless access point on that network, 32 | Additionally, if you access the website through an Admin account, you will have access to all networks with at least one wireless access point (doesn't matter if you placed it) 33 | 34 | On older minecraft versions where there is a security terminal, networks are identified through an security terminal, 35 | all players who wants to access the network should have their bio cards in the security terminal block (wild bio card is ignored for security reasons!) 36 | Admin account will have access to all networks on the server that have security terminal (doesn't matter it has your bio card) 37 | 38 | # Current features 39 | 40 | - AE2 terminal (item list+sorting+filtering) 41 | - **Monitor as many networks as you want!** 42 | - **Public mode, that allows the mod to works on servers with many independent players!** 43 | - Start a new order 44 | - Check any CPU status 45 | - Cancel any CPU order 46 | - Order tracking 47 | 48 | # Gallery 49 | 50 |
51 | Gallery 52 | 53 | image 54 | image 55 | image 56 | image 57 | 58 |
59 | 60 | # Security 61 | 62 | ## In public mode, 63 | 64 | all users on your server can create an account on the website and access their AE2 networks, 65 | passwords are saved on the server as a PBKDF2WithHmacSHA1 hash 66 | Once a user is authenticated, server generates a token valid for 1 hour/7 days and uses it to verify all requests after 67 | 68 | ## In public mode disabled, 69 | there is only an admin account which password is set in the config. 70 | token mechanism is the same as in public mode. 71 | 72 | **Note: localhost connections are automatically authenticated by default, you can change that in config!** 73 | 74 | # Requirements 75 | 76 | - An open port if you want to access the service outside your local network (configurable, terminal will be hosted at http://your-server-ip-or-domain:configured-port/ for example: http://server.kuba6000.pl:2324/) 77 | 78 | # How to use 79 | 80 | - Download the latest version for your game version from the releases page 81 | - Drop the mod in your server mods folder (only on the server!) (This also works on single player, but is not recommended) 82 | - Start the server 83 | - You can now find the config in /configs/ae2webintegration/ae2webintegration.toml (or .cfg on older mc versions). Edit the port number and password protection for your needs 84 | - **Disable public mode if you play by yourself** 85 | - [This step is done automatically by config watcher since 1.21.1, you can still force reload if it didn't worked!] Reload the config (/ae2webintegration reload) or restart the server 86 | - Make sure you have opened the configured port (firewall, redirections) if you want to use it on public internet 87 | - Now you can visit http://your-server-ip-or-domain:configured-port/ and login should appear on your browser! 88 | - **There is a default user Admin which password is set in the config** 89 | 90 | # Discord integration 91 | 92 | **Note: Discord integration is working only in public mode disabled!** 93 | - Create a webhook on your discord server and set it in the ae2webintegration config 94 | - image 95 | 96 | 97 | # Custom website 98 | 99 | - If you already have a web server and want to host the panel there, you can! 100 | - There is currently no API documentation... 101 | - Check out [Simple proxy site](https://github.com/kuba6000/AE2-Web-Integration/tree/master/example_website) ! 102 | - There you can find a simple website written in PHP ready to use, it's just simple proxy to API calls to the AE2 endpoint. 103 | 104 | # Compatibility 105 | 106 | The mod is currently implemented only on 1.21.1 (NEO), 1.20.1, 1.12.2 and 1.7.10 versions, although it might change in the future! 107 | A few remarks about compatibility: 108 | - 1.7.10 version is based on [GTNH fork of AE2](https://github.com/GTNewHorizons/Applied-Energistics-2-Unofficial) and [GTNH fork AE2FC for 1.7.10](https://github.com/GTNewHorizons/AE2FluidCraft-Rework) 109 | - 1.12.2 version is based on [AE2-UEL](https://github.com/AE2-UEL/Applied-Energistics-2) and [AE2FC for 1.12.2](https://github.com/AE2-UEL/AE2FluidCraft-Rework/) 110 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id 'com.gtnewhorizons.gtnhconvention' 4 | } 5 | -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Add your dependencies here. Supported configurations: 3 | * - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod 4 | * Available at runtime and compiletime for mods depending on this mod 5 | * - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API 6 | * Available at runtime but not compiletime for mods depending on this mod 7 | * - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods 8 | * Not available at all for mods depending on this mod, only visible at compiletime for this mod 9 | * - compileOnlyApi("g:n:v:c"): like compileOnly, but also visible at compiletime for mods depending on this mod 10 | * Available at compiletime but not runtime for mods depending on this mod 11 | * - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency 12 | * Not available at all for mods depending on this mod, only visible at runtime for this mod 13 | * - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly for dependencies present at both compiletime and runtime, 14 | * but not published as Maven dependencies - useful for RFG-deobfuscated dependencies or local testing 15 | * - runtimeOnly("g:n:v:c"): if you don't need this at compile time, but want it to be present at runtime 16 | * Available at runtime for mods depending on this mod 17 | * - annotationProcessor("g:n:v:c"): mostly for java compiler plugins, if you know you need this, use it, otherwise don't worry 18 | * - testCONFIG("g:n:v:c") - replace CONFIG by one of the above (except api), same as above but for the test sources instead of main 19 | * 20 | * - shadowImplementation("g:n:v:c"): effectively the same as API, but the dependency is included in your jar under a renamed package name 21 | * Requires you to enable usesShadowedDependencies in gradle.properties 22 | * 23 | * - compile("g:n:v:c"): deprecated, replace with "api" (works like the old "compile") or "implementation" (can be more efficient) 24 | * 25 | * You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed, 26 | * but use this sparingly as it can break using your mod as another mod's dependency if you're not careful. 27 | * 28 | * To depend on obfuscated jars you can use `devOnlyNonPublishable(rfg.deobf("dep:spec:1.2.3"))` to fetch an obfuscated jar from maven, 29 | * or `devOnlyNonPublishable(rfg.deobf(project.files("libs/my-mod-jar.jar")))` to use a file. 30 | * 31 | * Gradle names for some of the configuration can be misleading, compileOnlyApi and runtimeOnly both get published as dependencies in Maven, but compileOnly does not. 32 | * The buildscript adds runtimeOnlyNonPublishable to also have a runtime dependency that's not published. 33 | * 34 | * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph 35 | */ 36 | dependencies { 37 | api("com.github.GTNewHorizons:GTNHLib:0.6.39:dev") 38 | api("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-684-GTNH:dev") 39 | api("com.github.GTNewHorizons:AE2FluidCraft-Rework:1.4.114-gtnh:dev") 40 | runtimeOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.7.82-GTNH:dev") 41 | } 42 | -------------------------------------------------------------------------------- /example_website/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RedirectMatch 403 /login\.html$ 5 | RewriteRule ^(.*)$ index.php?API=$1 [QSA,L] -------------------------------------------------------------------------------- /example_website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuba6000/AE2-Web-Integration/9aa6e6fe27e58b443b077db3007b48e34e78f9a9/example_website/favicon.ico -------------------------------------------------------------------------------- /example_website/login.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:wght@300;400&display=swap'); 2 | *,html,body{ 3 | margin: 0; 4 | padding: 0; 5 | font-family: "Montserrat", sans-serif; 6 | font-optical-sizing: auto; 7 | font-style: normal; 8 | color: #e0e0e0; 9 | font-weight: 400; 10 | list-style-position: inside; 11 | } 12 | b{ 13 | font-weight: 500; 14 | } 15 | body{ 16 | background-color: #101219; 17 | color: #e0e0e0; 18 | } 19 | html,body{ 20 | height: 100%; 21 | width: 100%; 22 | } 23 | body>h1{ 24 | text-align: center; 25 | letter-spacing: 10px; 26 | font-weight: 800; 27 | color: #fff; 28 | font-size: 2.5em; 29 | } 30 | footer { 31 | position: fixed; 32 | Width: 100%; 33 | bottom: 0; 34 | background-color: #9292926b; 35 | padding: 10px; 36 | transition: 0.2s; 37 | } 38 | 39 | footer:hover{ 40 | background-color: #929292; 41 | } 42 | 43 | a{ 44 | color: #80cbc4; 45 | transition: 0.2s; 46 | } 47 | 48 | a:hover{ 49 | color: #a7ffeb; 50 | text-decoration: underline; 51 | } 52 | 53 | #maincontent{ 54 | width: 90%; 55 | height: 90%; 56 | margin: 0 auto; 57 | display: flex; 58 | } 59 | 60 | #main{ 61 | padding: 20px; 62 | margin: auto; 63 | background-color: #222736; 64 | border: black solid 1px; 65 | border-radius: 5px; 66 | 67 | } 68 | 69 | #mainrow 70 | { 71 | display: flex; 72 | flex-direction: row; 73 | } 74 | 75 | #maininfo{ 76 | text-align: center; 77 | } 78 | 79 | input, select { 80 | background-color: #2b3555; 81 | accent-color: #9b59b6; 82 | border: 0; 83 | padding: 5px; 84 | border-radius: 5px; 85 | margin-bottom: 10px; 86 | margin-top: 3px; 87 | } 88 | 89 | input[type='submit'], button{ 90 | background-color: #292f47; 91 | border: 0; 92 | border-radius: 5px; 93 | padding: 5px; 94 | transition: 0.2s; 95 | } 96 | 97 | input[type='submit']:hover, button:hover{ 98 | background-color: #363e5e; 99 | cursor: pointer; 100 | } 101 | 102 | input[type='checkbox']{ 103 | transform: translateY(1px); 104 | } 105 | 106 | form { 107 | width: 25vw; 108 | text-align: center; 109 | padding: 20px; 110 | border-right: rgb(216, 216, 216) dashed 1px; 111 | border-left: rgb(216, 216, 216) dashed 1px; 112 | border-collapse: collapse; 113 | } 114 | 115 | #cookiemonster{ 116 | z-index: 1000; 117 | position: fixed; 118 | top: 0; 119 | left: 0; 120 | width: 100%; 121 | height: 100%; 122 | background-color: rgba(0, 0, 0, 0.5); 123 | display: flex; 124 | backdrop-filter: blur(5px); 125 | } 126 | #cookiemonster > section{ 127 | margin: auto auto; 128 | display: flex; 129 | flex-direction: column; 130 | text-align: center; 131 | background-color: #222736; 132 | border: black solid 1px; 133 | border-radius: 5px; 134 | padding: 20px; 135 | } 136 | #cookiemonster > section > *{ 137 | margin: 3px; 138 | } 139 | 140 | h2{ 141 | font-weight: bold; 142 | } -------------------------------------------------------------------------------- /example_website/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AE2 11 | 12 | 13 |

UNIVERSAL WEB TERMINAL

14 | 27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 | This service requires authentication

35 |
36 |
37 |
38 |
39 |


40 | 41 |
42 |
43 | Or register

44 |
45 |
46 |
47 |
48 |

49 | 50 |
51 |
52 |
53 |
54 | 55 | 108 | 109 |

110 | 113 | 114 | -------------------------------------------------------------------------------- /example_website/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:wght@300;400&display=swap'); 2 | *,html,body{ 3 | margin: 0; 4 | padding: 0; 5 | font-family: "Montserrat", sans-serif; 6 | font-optical-sizing: auto; 7 | font-style: normal; 8 | color: #e0e0e0; 9 | font-weight: 400; 10 | } 11 | b{ 12 | font-weight: 500; 13 | } 14 | body{ 15 | background-color: #101219; 16 | color: #e0e0e0; 17 | } 18 | html,body{ 19 | height: 100%; 20 | width: 100%; 21 | } 22 | td{ 23 | padding: 10px; 24 | word-wrap: break-word; 25 | border: 3px #232e53 solid; 26 | border-radius: 1px; 27 | } 28 | td.active{ 29 | background-color: rgb(73, 105, 20); 30 | border-color: rgb(53, 77, 14); 31 | } 32 | td.pending{ 33 | background-color: rgb(92, 92, 15); 34 | border-color: rgb(71, 71, 11); 35 | } 36 | td.storage{ 37 | background-color: #2b3555; 38 | } 39 | td.missing{ 40 | background-color: rgb(112, 0, 0); 41 | border-color: rgb(85, 0, 0); 42 | } 43 | td img{ 44 | float: right; 45 | width: 32px; 46 | height: 32px; 47 | } 48 | body>h1{ 49 | text-align: center; 50 | letter-spacing: 10px; 51 | font-weight: 800; 52 | color: #fff; 53 | font-size: 2.5em; 54 | } 55 | 56 | #terminalcontainer{ 57 | width: 90%; 58 | min-height: 90%; 59 | border: black solid 1px; 60 | margin: 0 auto; 61 | border-collapse: collapse; 62 | display: flex; 63 | } 64 | 65 | #terminalgrid{ 66 | width: 90%; 67 | margin: 0 auto; 68 | border: black solid 1px; 69 | border-collapse: collapse; 70 | display: flex; 71 | flex-direction: column; 72 | } 73 | #terminalgrid>*{ 74 | display: flex; 75 | flex-direction: column; 76 | margin: 10px; 77 | } 78 | 79 | button{ 80 | background-color: #292f47; 81 | border: 0; 82 | border-radius: 5px; 83 | padding: 5px; 84 | transition: 0.2s; 85 | } 86 | button:hover{ 87 | background-color: #363e5e; 88 | cursor: pointer; 89 | } 90 | 91 | .redtext{ 92 | color:rgb(170, 0, 0); 93 | } 94 | 95 | #terminaltypes{ 96 | width: 10%; 97 | min-height: 100%; 98 | border-right: black solid 1px; 99 | float: left; 100 | } 101 | #terminaltypes button{ 102 | width: calc(100% - 10px); 103 | margin: 5px 5px; 104 | font-size: 110%; 105 | } 106 | 107 | #terminalwindow{ 108 | width: 80%; 109 | float: left; 110 | } 111 | 112 | #terminalheader{ 113 | width: calc(100% - 20px); 114 | min-height: 40px; 115 | height: fit-content; 116 | border-bottom: black solid 1px; 117 | font-size: 150%; 118 | padding: 10px; 119 | display: flex; 120 | flex-direction: column; 121 | } 122 | input, select { 123 | background-color: #2b3555; 124 | accent-color: #9b59b6; 125 | border: 0; 126 | padding: 5px; 127 | border-radius: 5px; 128 | } 129 | #terminalheader input{ 130 | font-size: 100%; 131 | background-color: #2b3555; 132 | border: 0; 133 | border-radius: 5px; 134 | transition: 0.2s; 135 | padding: 5px; 136 | } 137 | #terminalheader input:hover{ 138 | background-color: #384572; 139 | } 140 | #terminalheader button{ 141 | font-size: 110%; 142 | float:right; 143 | margin-left: 10px; 144 | } 145 | #terminalcontent{ 146 | width: 100%; 147 | min-height: 95%; 148 | } 149 | #terminalcontent table{ 150 | width: 100%; 151 | table-layout: fixed; 152 | } 153 | #terminalcontent td{ 154 | height: auto; 155 | } 156 | #terminalcontent td.button{ 157 | background-color: #2b3555; 158 | border: 0; 159 | border-radius: 2px; 160 | padding: 5px; 161 | transition: 0.2s; 162 | } 163 | #terminalcontent td.button:hover{ 164 | background-color: #3b4874; 165 | cursor: pointer; 166 | } 167 | #terminalsubcontainer{ 168 | width: 10%; 169 | float: left; 170 | border-left: solid black 1px; 171 | font-size: 110%; 172 | } 173 | 174 | #terminalsubcontainer header{ 175 | border-bottom: black dotted 1px; 176 | display: block; 177 | width: 100%; 178 | text-align: center; 179 | font-size: 130%; 180 | font-weight: 500; 181 | margin-bottom: 10px; 182 | } 183 | #terminalsubcontainer section{ 184 | text-align: center; 185 | } 186 | #terminalsubcontainer > * > section > *{ 187 | margin: 5px auto; 188 | } 189 | 190 | #terminalCPUList button, #terminalCPUListForJob button{ 191 | width: 100%; 192 | margin: 0; 193 | border-bottom: black dotted 1px; 194 | word-wrap: break-word; 195 | } 196 | #terminalCPUList button.selected, #terminalCPUListForJob button.selected{ 197 | background-color: rgb(0, 137, 155); 198 | } 199 | #terminalCPUListForJob button.invalid{ 200 | background-color: rgb(155, 23, 0); 201 | } 202 | #terminalCPUListForJob button.mergable{ 203 | background-color: rgb(170, 159, 0); 204 | } 205 | #terminalCPUListForJob button.mergable.selected{ 206 | background-color: rgb(0, 165, 194); 207 | } 208 | 209 | #terminalHistoryDetails{ 210 | padding: 5px; 211 | } 212 | 213 | #overlay { 214 | position: fixed; /* Sit on top of the page content */ 215 | display: none; /* Hidden by default */ 216 | width: 100%; /* Full width (cover the whole page) */ 217 | height: 100%; /* Full height (cover the whole page) */ 218 | top: 0; 219 | left: 0; 220 | right: 0; 221 | bottom: 0; 222 | background-color: rgba(0,0,0,0.5); /* Black background with opacity */ 223 | z-index: 2; /* Specify a stack order in case you're using a different order for other elements */ 224 | } 225 | #overlay>#overlaytext{ 226 | position: absolute; 227 | top: 50%; 228 | left: 50%; 229 | font-size: 50px; 230 | font-weight: 900; 231 | color: #e0e0e0; 232 | transform: translate(-50%,-50%); 233 | -ms-transform: translate(-50%,-50%); 234 | } 235 | 236 | #alertoverlay { 237 | position: fixed; 238 | display: flex; 239 | flex-direction: column; 240 | width: 15%; 241 | height: fit-content; 242 | top: 5px; 243 | left: 5px; 244 | right: 0; 245 | bottom: 0; 246 | background-color: rgba(0, 0, 0, 0); 247 | z-index: 2; 248 | } 249 | #alertoverlay > *{ 250 | width: 100%; 251 | border: black solid 1px; 252 | border-radius: 5px; 253 | background-color: #cc404049; 254 | padding: 10px; 255 | cursor: pointer; 256 | } 257 | 258 | #alertoverlay > *::after{ 259 | content: "X"; 260 | float: right; 261 | color: #e0e0e0; 262 | 263 | } 264 | 265 | #topMessages{ 266 | position: fixed; 267 | width: 100%; 268 | text-align: center; 269 | background-color: #929292; 270 | transition: 0.2s; 271 | } 272 | 273 | #topMessages > *{ 274 | display: block; 275 | text-align: center; 276 | margin: 10px 0; 277 | font-size: 130%; 278 | } 279 | 280 | #topMessages > * > button{ 281 | float: right; 282 | margin-right: 10px; 283 | } 284 | 285 | 286 | footer { 287 | position: fixed; 288 | Width: 100%; 289 | bottom: 0; 290 | background-color: #9292926b; 291 | padding: 10px; 292 | transition: 0.2s; 293 | } 294 | 295 | footer:hover{ 296 | background-color: #929292; 297 | } 298 | 299 | .note{ 300 | color: rgb(80, 80, 80); 301 | font-style: italic; 302 | font-size: 90%; 303 | } 304 | .note::before{ 305 | content: "ⓘ "; 306 | } 307 | 308 | a{ 309 | color: #80cbc4; 310 | transition: 0.2s; 311 | } 312 | 313 | a:hover{ 314 | color: #a7ffeb; 315 | text-decoration: underline; 316 | } 317 | 318 | option:disabled{ 319 | color: rgb(170, 170, 170); 320 | } 321 | option:checked{ 322 | color: light-dark(rgb(16, 16, 16), rgb(255, 255, 255)); 323 | } 324 | 325 | .minecraftSpecialFormat_0{ 326 | color: #000; 327 | } 328 | 329 | .minecraftSpecialFormat_1{ 330 | color: #0000AA; 331 | } 332 | 333 | .minecraftSpecialFormat_2{ 334 | color: #00AA00; 335 | } 336 | 337 | .minecraftSpecialFormat_3{ 338 | color: #00AAAA; 339 | } 340 | 341 | .minecraftSpecialFormat_4{ 342 | color: #AA0000; 343 | } 344 | 345 | .minecraftSpecialFormat_5{ 346 | color: #AA00AA; 347 | } 348 | 349 | .minecraftSpecialFormat_6{ 350 | color: #FFAA00; 351 | } 352 | 353 | .minecraftSpecialFormat_7{ 354 | color: #AAAAAA; 355 | } 356 | 357 | .minecraftSpecialFormat_8{ 358 | color: #555555; 359 | } 360 | 361 | .minecraftSpecialFormat_9{ 362 | color: #5555FF; 363 | } 364 | 365 | .minecraftSpecialFormat_a{ 366 | color: #55FF55; 367 | } 368 | 369 | .minecraftSpecialFormat_b{ 370 | color: #55FFFFFF; 371 | } 372 | 373 | .minecraftSpecialFormat_c{ 374 | color: #FF5555; 375 | } 376 | 377 | .minecraftSpecialFormat_d{ 378 | color: #FF55FF; 379 | } 380 | 381 | .minecraftSpecialFormat_e{ 382 | color: #FFFF55; 383 | } 384 | 385 | .minecraftSpecialFormat_f{ 386 | color: #FFFFFF; 387 | } 388 | 389 | .minecraftSpecialFormat_k{ 390 | /* no implementation? */ 391 | } 392 | 393 | .minecraftSpecialFormat_l{ 394 | font-weight: bold; 395 | } 396 | 397 | .minecraftSpecialFormat_m{ 398 | text-decoration: line-through; 399 | } 400 | 401 | .minecraftSpecialFormat_n{ 402 | text-decoration: underline; 403 | } 404 | 405 | .minecraftSpecialFormat_o{ 406 | font-style: italic; 407 | } 408 | 409 | .minecraftSpecialFormat_r{ 410 | color: black; 411 | } 412 | 413 | .collapsible:after { 414 | content: '\02795'; 415 | font-size: 13px; 416 | color: #e0e0e0; 417 | margin-left: 5px; 418 | } 419 | 420 | .collapsible.active:after { 421 | content: "\2796"; 422 | } 423 | 424 | .mobile-only{ 425 | display: none; 426 | } 427 | 428 | @media screen and (max-width: 1300px){ 429 | body{ 430 | font-size: 12px; 431 | } 432 | } 433 | 434 | @media screen and (max-width: 1000px){ 435 | #terminalheader{ 436 | border-top: black solid 1px; 437 | } 438 | #terminalheader > * > *{ 439 | width: 100%; 440 | float: right; 441 | margin-top: 5px; 442 | margin-left: 10px; 443 | } 444 | #terminalheader:first-child{ 445 | text-align: center; 446 | font-size: 200% !important; 447 | font-weight: bold !important; 448 | } 449 | #terminalheader > * > input{ 450 | width: calc(100% - 10px); 451 | float: right; 452 | margin-top: 5px; 453 | margin-left: 10px; 454 | } 455 | #terminalsubcontainer{ 456 | width: 100%; 457 | float: none; 458 | order: 2; 459 | border-left: none; 460 | border-top: black solid 1px; 461 | } 462 | #terminalsubcontainer header{ 463 | border-bottom: none; 464 | display: block; 465 | width: 100%; 466 | text-align: center; 467 | font-size: 130%; 468 | font-weight: 500; 469 | margin-bottom: 10px; 470 | } 471 | #terminaltypes{ 472 | width: 100%; 473 | float: none; 474 | order: 1; 475 | } 476 | #terminalwindow{ 477 | width: 100%; 478 | float: none; 479 | order: 3; 480 | } 481 | #terminalcontainer{ 482 | width: 100%; 483 | display: flex; 484 | flex-direction: column; 485 | } 486 | #terminalgrid { 487 | width: 100%; 488 | display: flex; 489 | flex-direction: column; 490 | } 491 | #terminalgrid > *{ 492 | width: calc(100% - 10px) !important; 493 | margin: 5px 5px; 494 | margin-top: 10px !important; 495 | font-size: 150% !important; 496 | } 497 | #terminaltypes button{ 498 | width: calc(50% - 10px); 499 | margin: 5px 5px; 500 | float: left; 501 | } 502 | .mobile-only{ 503 | display: block; 504 | width: calc(100% - 10px) !important; 505 | margin: 5px 5px; 506 | text-align: center; 507 | margin-top: 10px !important; 508 | font-size: 150% !important; 509 | } 510 | .mobile-hidden{ 511 | display: none !important; 512 | } 513 | #terminalOptions > section{ 514 | display: none; 515 | } 516 | #terminalsubcontainersettings > section{ 517 | display: none; 518 | font-size: 150%; 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable. 2 | # LOCAL to test local config updates. 3 | gtnh.settings.blowdryerTag = 0.2.2 4 | 5 | # Human-readable mod name, available for mcmod.info population. 6 | modName = AE2WebIntegration 7 | 8 | # Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation. 9 | # Conventionally lowercase. 10 | modId = ae2webintegration 11 | 12 | # Root package of the mod, used to find various classes in other properties, 13 | # mcmod.info substitution, enabling assertions in run tasks, etc. 14 | modGroup = pl.kuba6000.ae2webintegration 15 | 16 | # Whether to use modGroup as the maven publishing group. 17 | # When false, com.github.GTNewHorizons is used. 18 | useModGroupForPublishing = false 19 | 20 | # Updates your build.gradle and settings.gradle automatically whenever an update is available. 21 | autoUpdateBuildScript = false 22 | 23 | # Version of Minecraft to target 24 | minecraftVersion = 1.7.10 25 | 26 | # Version of Minecraft Forge to target 27 | forgeVersion = 10.13.4.1614 28 | 29 | # Specify an MCP channel for dependency deobfuscation and the deobfParams task. 30 | channel = stable 31 | 32 | # Specify an MCP mappings version for dependency deobfuscation and the deobfParams task. 33 | mappingsVersion = 12 34 | 35 | # Defines other MCP mappings for dependency deobfuscation. 36 | remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/ 37 | 38 | # Select a default username for testing your mod. You can always override this per-run by running 39 | # `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. 40 | developmentEnvironmentUserName = Developer 41 | 42 | # Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. 43 | # See https://github.com/bsideup/jabel for details on how this works. 44 | enableModernJavaSyntax = true 45 | 46 | # Enables injecting missing generics into the decompiled source code for a better coding experience. 47 | # Turns most publicly visible List, Map, etc. into proper List, Map types. 48 | enableGenericInjection = true 49 | 50 | # Generate a class with a String field for the mod version named as defined below. 51 | # If generateGradleTokenClass is empty or not missing, no such class will be generated. 52 | # If gradleTokenVersion is empty or missing, the field will not be present in the class. 53 | generateGradleTokenClass = pl.kuba6000.ae2webintegration.Tags 54 | 55 | # Name of the token containing the project's current version to generate/replace. 56 | gradleTokenVersion = VERSION 57 | 58 | # [DEPRECATED] 59 | # Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java 60 | # public static final String VERSION = "GRADLETOKEN_VERSION"; 61 | # The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's 62 | # version in @Mod([...], version = VERSION, [...]). 63 | # Leave these properties empty to skip individual token replacements. 64 | replaceGradleTokenInFile = 65 | 66 | # In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can 67 | # leave this property empty. 68 | # Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api 69 | apiPackage = 70 | 71 | # Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/ 72 | # There can be multiple files in a space-separated list. 73 | # Example value: mymodid_at.cfg nei_at.cfg 74 | accessTransformersFile = 75 | 76 | # Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled! 77 | usesMixins = true 78 | 79 | # Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main. 80 | # This can speed up compile times thanks to not running the mixin annotation processor on all input sources. 81 | # Mixin classes will have access to "main" classes, but not the other way around. 82 | separateMixinSourceSet = 83 | 84 | # Adds some debug arguments like verbose output and class export. 85 | usesMixinDebug = false 86 | 87 | # Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. 88 | mixinPlugin = ae2interface.mixins.MixinPlugin 89 | 90 | # Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail! 91 | mixinsPackage = ae2interface.mixins 92 | 93 | # Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! 94 | # This parameter is for legacy compatibility only 95 | # Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin 96 | coreModClass = 97 | 98 | # If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class 99 | # that is annotated with @Mod) you want this to be true. When in doubt: leave it on false! 100 | containsMixinsAndOrCoreModOnly = false 101 | 102 | # Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins. 103 | forceEnableMixins = false 104 | 105 | # If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your 106 | # responsibility to check the license and request permission for distribution if required. 107 | usesShadowedDependencies = false 108 | 109 | # If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access 110 | # their own classes, making the minimization unreliable. 111 | minimizeShadowedDependencies = true 112 | 113 | # If disabled, won't rename the shadowed classes. 114 | relocateShadowedDependencies = true 115 | 116 | # Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories. 117 | includeWellKnownRepositories = true 118 | 119 | # A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated 120 | # list of strings, with the acceptable keys being(case does not matter): 121 | # cursemaven 122 | # modrinth 123 | excludeWellKnownRepositories = 124 | 125 | # Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven. 126 | # Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables. 127 | # If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle. 128 | usesMavenPublishing = true 129 | 130 | # Maven repository to publish the mod to. 131 | # mavenPublishUrl = https\://nexus.gtnewhorizons.com/repository/releases/ 132 | 133 | # Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token. 134 | # 135 | # The project's ID on Modrinth. Can be either the slug or the ID. 136 | # Leave this empty if you don't want to publish to Modrinth. 137 | modrinthProjectId = 138 | 139 | # The project's relations on Modrinth. You can use this to refer to other projects on Modrinth. 140 | # Syntax: scope1-type1:name1;scope2-type2:name2;... 141 | # Where scope can be one of [required, optional, incompatible, embedded], 142 | # type can be one of [project, version], 143 | # and the name is the Modrinth project or version slug/id of the other mod. 144 | # Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech 145 | # Note: UniMixins is automatically set as a required dependency if usesMixins = true. 146 | modrinthRelations = 147 | 148 | # Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens. 149 | # 150 | # The project's numeric ID on CurseForge. You can find this in the About Project box. 151 | # Leave this empty if you don't want to publish on CurseForge. 152 | curseForgeProjectId = 153 | 154 | # The project's relations on CurseForge. You can use this to refer to other projects on CurseForge. 155 | # Syntax: type1:name1;type2:name2;... 156 | # Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], 157 | # and the name is the CurseForge project slug of the other mod. 158 | # Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft 159 | # Note: UniMixins is automatically set as a required dependency if usesMixins = true. 160 | curseForgeRelations = 161 | 162 | # Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older 163 | # projects. New projects should not use this parameter. 164 | # customArchiveBaseName = 165 | 166 | # Optional parameter to have the build automatically fail if an illegal version is used. 167 | # This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'. 168 | # The check is ONLY performed if the version is a git tag. 169 | # Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+ 170 | # versionPattern = 171 | 172 | # Uncomment to prevent the source code from being published. 173 | # noPublishedSources = true 174 | 175 | # Uncomment this to disable Spotless checks. 176 | # This should only be uncommented to keep it easier to sync with upstream/other forks. 177 | # That is, if there is no other active fork/upstream, NEVER change this. 178 | # disableSpotless = true 179 | 180 | # Uncomment this to disable Checkstyle checks (currently wildcard import check). 181 | # disableCheckstyle = true 182 | 183 | # Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle" 184 | # (force use delegated build). 185 | # This is meant to be set in $HOME/.gradle/gradle.properties. 186 | # e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build. 187 | # WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want! 188 | # Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you. 189 | # ideaOverrideBuildType = idea 190 | 191 | # Whether IDEA should run spotless checks when pressing the Build button. 192 | # This is meant to be set in $HOME/.gradle/gradle.properties. 193 | # ideaCheckSpotlessOnBuild = true 194 | 195 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuba6000/AE2-Web-Integration/9aa6e6fe27e58b443b077db3007b48e34e78f9a9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /repositories.gradle: -------------------------------------------------------------------------------- 1 | // Add any additional repositories for your dependencies here. 2 | 3 | repositories { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | pluginManagement { 3 | repositories { 4 | maven { 5 | // RetroFuturaGradle 6 | name "GTNH Maven" 7 | url "https://nexus.gtnewhorizons.com/repository/public/" 8 | mavenContent { 9 | includeGroup("com.gtnewhorizons") 10 | includeGroupByRegex("com\\.gtnewhorizons\\..+") 11 | } 12 | } 13 | gradlePluginPortal() 14 | mavenCentral() 15 | mavenLocal() 16 | } 17 | } 18 | 19 | plugins { 20 | id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.41' 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/AE2WebIntegration.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import appeng.me.cache.SecurityCache; 7 | import cpw.mods.fml.common.Mod; 8 | import cpw.mods.fml.common.event.FMLInitializationEvent; 9 | import cpw.mods.fml.common.event.FMLPostInitializationEvent; 10 | import cpw.mods.fml.common.event.FMLPreInitializationEvent; 11 | import cpw.mods.fml.common.event.FMLServerStartingEvent; 12 | import pl.kuba6000.ae2webintegration.Tags; 13 | import pl.kuba6000.ae2webintegration.ae2interface.implementations.AE; 14 | import pl.kuba6000.ae2webintegration.core.api.IAEWebInterface; 15 | 16 | @Mod( 17 | modid = AE2WebIntegration.MODID, 18 | version = Tags.VERSION, 19 | name = "AE2WebIntegration-Interface", 20 | acceptedMinecraftVersions = "[1.7.10]", 21 | acceptableRemoteVersions = "*") 22 | public class AE2WebIntegration { 23 | 24 | public static final String MODID = "ae2webintegration-interface"; 25 | public static final Logger LOG = LogManager.getLogger(MODID); 26 | 27 | @Mod.EventHandler 28 | public void preInit(FMLPreInitializationEvent event) {} 29 | 30 | @Mod.EventHandler 31 | public void init(FMLInitializationEvent event) { 32 | IAEWebInterface.getInstance() 33 | .initAEInterface(AE.instance); 34 | } 35 | 36 | @Mod.EventHandler 37 | public void postInit(FMLPostInitializationEvent event) { 38 | SecurityCache.registerOpPlayer( 39 | IAEWebInterface.getInstance() 40 | .getAEWebGameProfile()); 41 | } 42 | 43 | @Mod.EventHandler 44 | public void serverStarting(FMLServerStartingEvent event) { 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/CraftingMediumTracker.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface; 2 | 3 | import java.util.IdentityHashMap; 4 | 5 | import appeng.api.networking.IGrid; 6 | import appeng.api.networking.crafting.ICraftingMedium; 7 | import appeng.api.networking.crafting.ICraftingProvider; 8 | import appeng.api.util.IInterfaceViewable; 9 | import appeng.me.cache.CraftingGridCache; 10 | 11 | public class CraftingMediumTracker { 12 | 13 | public static final IdentityHashMap> mediumToViewable = new IdentityHashMap<>(); 14 | private static boolean isUpdatingPatterns = false; 15 | private static ICraftingProvider currentCraftingProvider = null; 16 | 17 | public static void updatingPatterns(CraftingGridCache craftingGrid, IGrid grid) { 18 | mediumToViewable.put(grid, new IdentityHashMap<>()); 19 | isUpdatingPatterns = true; 20 | } 21 | 22 | public static void provideCrafting(CraftingGridCache craftingGrid, IGrid grid, ICraftingProvider provider) { 23 | if (!isUpdatingPatterns) return; 24 | currentCraftingProvider = provider; 25 | } 26 | 27 | public static void addCraftingOption(CraftingGridCache craftingGrid, IGrid grid, ICraftingMedium medium) { 28 | if (!isUpdatingPatterns) return; 29 | if (currentCraftingProvider == null) return; 30 | if (currentCraftingProvider instanceof IInterfaceViewable viewable && !mediumToViewable.get(grid) 31 | .containsKey(medium)) mediumToViewable.get(grid) 32 | .put(medium, viewable); 33 | } 34 | 35 | public static void doneUpdatingPatterns(CraftingGridCache craftingGrid, IGrid grid) { 36 | isUpdatingPatterns = false; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/implementations/AE.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.implementations; 2 | 3 | import java.util.Iterator; 4 | 5 | import appeng.api.AEApi; 6 | import appeng.core.worlddata.WorldData; 7 | import appeng.hooks.TickHandler; 8 | import appeng.me.Grid; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IAE; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEPlayerData; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 13 | 14 | public class AE implements IAE { 15 | 16 | public static AE instance = new AE(); 17 | 18 | public static AE getInstance() { 19 | return instance; 20 | } 21 | 22 | static class AEGridIterable implements Iterable { 23 | 24 | @Override 25 | public java.util.Iterator iterator() { 26 | return new java.util.Iterator<>() { 27 | 28 | private final Iterator iterator = TickHandler.INSTANCE.getGridList() 29 | .iterator(); 30 | 31 | @Override 32 | public boolean hasNext() { 33 | return iterator.hasNext(); 34 | } 35 | 36 | @Override 37 | public IAEGrid next() { 38 | return (IAEGrid) iterator.next(); 39 | } 40 | }; 41 | } 42 | } 43 | 44 | @Override 45 | public Iterable web$getGrids() { 46 | return new AEGridIterable(); 47 | } 48 | 49 | @Override 50 | public IItemList web$createItemList() { 51 | return (IItemList) (Object) AEApi.instance() 52 | .storage() 53 | .createItemList(); 54 | } 55 | 56 | @Override 57 | public IAEPlayerData web$getPlayerData() { 58 | return (IAEPlayerData) WorldData.instance() 59 | .playerData(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/CraftingCPUClusterMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2; 2 | 3 | import java.util.Map; 4 | 5 | import net.minecraft.inventory.InventoryCrafting; 6 | 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | import com.llamalad7.mixinextras.sugar.Local; 16 | 17 | import appeng.api.networking.IGrid; 18 | import appeng.api.networking.crafting.ICraftingMedium; 19 | import appeng.api.networking.crafting.ICraftingPatternDetails; 20 | import appeng.api.storage.data.IAEItemStack; 21 | import appeng.api.storage.data.IAEStack; 22 | import appeng.api.util.IInterfaceViewable; 23 | import appeng.me.cluster.implementations.CraftingCPUCluster; 24 | import pl.kuba6000.ae2webintegration.ae2interface.CraftingMediumTracker; 25 | import pl.kuba6000.ae2webintegration.core.api.IAEMixinCallbacks; 26 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingPatternDetails; 27 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 28 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 29 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 30 | import pl.kuba6000.ae2webintegration.core.interfaces.IPatternProviderViewable; 31 | 32 | @Mixin(value = CraftingCPUCluster.class, remap = false) 33 | public class CraftingCPUClusterMixin { 34 | 35 | @Shadow 36 | private IGrid getGrid() { 37 | throw new IllegalStateException("Mixin failed to apply"); 38 | } 39 | 40 | @Shadow 41 | private void postCraftingStatusChange(final IAEItemStack diff) { 42 | throw new IllegalStateException("Mixin failed to apply"); 43 | } 44 | 45 | @Inject(method = "postCraftingStatusChange", at = @At("HEAD")) 46 | void ae2webintegration$postCraftingStatusChange(IAEItemStack diff, CallbackInfo ci) { 47 | IAEMixinCallbacks.getInstance() 48 | .craftingStatusPostedUpdate((ICraftingCPUCluster) this, (IItemStack) diff); 49 | } 50 | 51 | @Inject(method = "completeJob", at = @At("HEAD")) 52 | void ae2webintegration$completeJob(CallbackInfo ci) { 53 | IAEMixinCallbacks.getInstance() 54 | .jobCompleted((IAEGrid) getGrid(), (ICraftingCPUCluster) this); 55 | } 56 | 57 | @Inject(method = "cancel", at = @At("HEAD")) 58 | void ae2webintegration$cancel(CallbackInfo ci) { 59 | IAEMixinCallbacks.getInstance() 60 | .jobCancelled((IAEGrid) getGrid(), (ICraftingCPUCluster) this); 61 | } 62 | 63 | @Inject( 64 | method = "injectItems", 65 | at = @At( 66 | value = "INVOKE", 67 | target = "Lappeng/api/storage/data/IAEItemStack;setStackSize(J)Lappeng/api/storage/data/IAEStack;", 68 | shift = At.Shift.AFTER, 69 | ordinal = 2)) 70 | void ae2webintegration$fixCpuCluster(CallbackInfoReturnable cir, @Local(ordinal = 1) IAEItemStack is) { 71 | postCraftingStatusChange(is); 72 | } 73 | 74 | @Redirect( 75 | method = "executeCrafting", 76 | at = @At( 77 | value = "INVOKE", 78 | target = "Lappeng/api/networking/crafting/ICraftingMedium;pushPattern(Lappeng/api/networking/crafting/ICraftingPatternDetails;Lnet/minecraft/inventory/InventoryCrafting;)Z")) 79 | private boolean ae2webintegration$pushPattern(ICraftingMedium medium, ICraftingPatternDetails details, 80 | InventoryCrafting ic) { 81 | if (medium.pushPattern(details, ic)) { 82 | IInterfaceViewable viewable = null; 83 | Map mediumToViewable = CraftingMediumTracker.mediumToViewable 84 | .get(getGrid()); 85 | if (mediumToViewable != null) { 86 | viewable = mediumToViewable.get(medium); 87 | } 88 | IAEMixinCallbacks.getInstance() 89 | .pushedPattern( 90 | (ICraftingCPUCluster) this, 91 | (IPatternProviderViewable) viewable, 92 | (IAECraftingPatternDetails) details); 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/CraftingGridCacheMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2; 2 | 3 | import org.spongepowered.asm.mixin.Final; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Shadow; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import appeng.api.networking.IGrid; 12 | import appeng.api.networking.crafting.ICraftingJob; 13 | import appeng.api.networking.crafting.ICraftingLink; 14 | import appeng.api.networking.crafting.ICraftingMedium; 15 | import appeng.api.networking.crafting.ICraftingPatternDetails; 16 | import appeng.api.networking.crafting.ICraftingProvider; 17 | import appeng.api.networking.crafting.ICraftingProviderHelper; 18 | import appeng.api.networking.crafting.ICraftingRequester; 19 | import appeng.api.networking.security.BaseActionSource; 20 | import appeng.me.cache.CraftingGridCache; 21 | import appeng.me.cluster.implementations.CraftingCPUCluster; 22 | import pl.kuba6000.ae2webintegration.ae2interface.CraftingMediumTracker; 23 | import pl.kuba6000.ae2webintegration.core.api.IAEMixinCallbacks; 24 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 25 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 26 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 27 | 28 | @Mixin(value = CraftingGridCache.class, remap = false) 29 | public class CraftingGridCacheMixin { 30 | 31 | @Final 32 | @Shadow 33 | private IGrid grid; 34 | 35 | @Redirect( 36 | method = "submitJob(Lappeng/api/networking/crafting/ICraftingJob;Lappeng/api/networking/crafting/ICraftingRequester;Lappeng/api/networking/crafting/ICraftingCPU;ZLappeng/api/networking/security/BaseActionSource;Z)Lappeng/api/networking/crafting/ICraftingLink;", 37 | at = @At( 38 | value = "INVOKE", 39 | target = "Lappeng/me/cluster/implementations/CraftingCPUCluster;submitJob(Lappeng/api/networking/IGrid;Lappeng/api/networking/crafting/ICraftingJob;Lappeng/api/networking/security/BaseActionSource;Lappeng/api/networking/crafting/ICraftingRequester;)Lappeng/api/networking/crafting/ICraftingLink;")) 40 | ICraftingLink ae2webintegration$submitJob(CraftingCPUCluster instance, IGrid craftID, ICraftingJob whatLink, 41 | BaseActionSource list, ICraftingRequester e) { 42 | boolean isMerging = false; 43 | if (instance.isBusy()) { 44 | isMerging = true; 45 | } 46 | ICraftingLink link = instance.submitJob(craftID, whatLink, list, e); 47 | if (link != null) { // job started successfully 48 | boolean isMachine = e != null || list.isMachine(); 49 | IAEMixinCallbacks.getInstance() 50 | .jobStarted( 51 | (ICraftingCPUCluster) (Object) instance, 52 | (IAECraftingGrid) this, 53 | (IAEGrid) grid, 54 | isMerging, 55 | !isMachine); 56 | } 57 | return link; 58 | } 59 | 60 | @Inject( 61 | method = "updatePatterns", 62 | at = @At(value = "INVOKE", target = "Ljava/util/Map;clear()V", ordinal = 0, shift = At.Shift.AFTER)) 63 | void ae2webintegration$updatePatternsStart(CallbackInfo ci) { 64 | CraftingMediumTracker.updatingPatterns((CraftingGridCache) (Object) this, grid); 65 | } 66 | 67 | @Redirect( 68 | method = "updatePatterns", 69 | at = @At( 70 | value = "INVOKE", 71 | target = "Lappeng/api/networking/crafting/ICraftingProvider;provideCrafting(Lappeng/api/networking/crafting/ICraftingProviderHelper;)V")) 72 | void ae2webintegration$provideCrafting(ICraftingProvider instance, 73 | ICraftingProviderHelper iCraftingProviderHelper) { 74 | CraftingMediumTracker.provideCrafting((CraftingGridCache) (Object) this, grid, instance); 75 | instance.provideCrafting(iCraftingProviderHelper); 76 | } 77 | 78 | @Inject(method = "addCraftingOption", at = @At("HEAD")) 79 | void ae2webintegration$addCraftingOption(ICraftingMedium medium, ICraftingPatternDetails api, CallbackInfo ci) { 80 | CraftingMediumTracker.addCraftingOption((CraftingGridCache) (Object) this, grid, medium); 81 | } 82 | 83 | @Inject(method = "updatePatterns", at = @At(value = "TAIL")) 84 | void ae2webintegration$updatePatternsEnd(CallbackInfo ci) { 85 | CraftingMediumTracker.doneUpdatingPatterns((CraftingGridCache) (Object) this, grid); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AECraftingCPUClusterMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.Shadow; 5 | import org.spongepowered.asm.mixin.Unique; 6 | 7 | import appeng.api.networking.crafting.CraftingItemList; 8 | import appeng.api.storage.data.IAEItemStack; 9 | import appeng.me.cluster.implementations.CraftingCPUCluster; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 13 | 14 | @Mixin(value = CraftingCPUCluster.class, remap = false) 15 | public abstract class AECraftingCPUClusterMixin implements ICraftingCPUCluster { 16 | 17 | @Shadow 18 | private appeng.api.storage.data.IItemList waitingFor; 19 | 20 | @Unique 21 | private int web$internalID = -1; 22 | 23 | @Override 24 | public void web$setInternalID(int id) { 25 | web$internalID = id; 26 | } 27 | 28 | @Override 29 | public boolean web$hasCustomName() { 30 | return !((CraftingCPUCluster) (Object) this).getName() 31 | .isEmpty(); 32 | } 33 | 34 | @Override 35 | public String web$getName() { 36 | return web$hasCustomName() ? ((CraftingCPUCluster) (Object) this).getName() : ("CPU #" + web$internalID); 37 | } 38 | 39 | @Override 40 | public long web$getAvailableStorage() { 41 | return ((CraftingCPUCluster) (Object) this).getAvailableStorage(); 42 | } 43 | 44 | @Unique 45 | private boolean web$isUsedStorageAvailable = true; 46 | 47 | @Unique 48 | private boolean web$usedStorageInitialized = false; 49 | 50 | @Override 51 | public long web$getUsedStorage() { 52 | if (!web$usedStorageInitialized) { 53 | web$usedStorageInitialized = true; 54 | try { 55 | appeng.me.cluster.implementations.CraftingCPUCluster.class.getDeclaredMethod("getUsedStorage"); 56 | } catch (NoSuchMethodException e) { 57 | web$isUsedStorageAvailable = false; 58 | return -1L; 59 | } 60 | } 61 | if (!web$isUsedStorageAvailable) return -1L; 62 | return ((CraftingCPUCluster) (Object) this).getUsedStorage(); 63 | } 64 | 65 | @Override 66 | public long web$getCoProcessors() { 67 | return ((CraftingCPUCluster) (Object) this).getCoProcessors(); 68 | } 69 | 70 | @Override 71 | public boolean web$isBusy() { 72 | return ((CraftingCPUCluster) (Object) this).isBusy(); 73 | } 74 | 75 | @Override 76 | public void web$cancel() { 77 | ((CraftingCPUCluster) (Object) this).cancel(); 78 | } 79 | 80 | @Override 81 | public IItemStack web$getFinalOutput() { 82 | return (IItemStack) ((CraftingCPUCluster) (Object) this).getFinalOutput(); 83 | } 84 | 85 | @Override 86 | public void web$getActiveItems(IItemList list) { 87 | ((CraftingCPUCluster) (Object) this) 88 | .getListOfItem((appeng.api.storage.data.IItemList) (Object) list, CraftingItemList.ACTIVE); 89 | } 90 | 91 | @Override 92 | public void web$getPendingItems(IItemList list) { 93 | ((CraftingCPUCluster) (Object) this) 94 | .getListOfItem((appeng.api.storage.data.IItemList) (Object) list, CraftingItemList.PENDING); 95 | } 96 | 97 | @Override 98 | public void web$getStorageItems(IItemList list) { 99 | ((CraftingCPUCluster) (Object) this) 100 | .getListOfItem((appeng.api.storage.data.IItemList) (Object) list, CraftingItemList.STORAGE); 101 | } 102 | 103 | @Override 104 | public IItemList web$getWaitingFor() { 105 | return (IItemList) (Object) waitingFor; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AECraftingJobMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.networking.crafting.ICraftingJob; 6 | import appeng.api.storage.data.IAEItemStack; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 9 | 10 | @Mixin(value = ICraftingJob.class, remap = false) 11 | public interface AECraftingJobMixin extends IAECraftingJob { 12 | 13 | @Override 14 | public default boolean web$isSimulation() { 15 | return ((ICraftingJob) (Object) this).isSimulation(); 16 | } 17 | 18 | @Override 19 | public default long web$getByteTotal() { 20 | return ((ICraftingJob) (Object) this).getByteTotal(); 21 | } 22 | 23 | @Override 24 | public default void web$populatePlan(IItemList plan) { 25 | ((ICraftingJob) (Object) this).populatePlan((appeng.api.storage.data.IItemList) (Object) plan); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AECraftingPatternDetailsMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.networking.crafting.ICraftingPatternDetails; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingPatternDetails; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 8 | 9 | @Mixin(value = ICraftingPatternDetails.class, remap = false) 10 | public interface AECraftingPatternDetailsMixin extends IAECraftingPatternDetails { 11 | 12 | @Override 13 | public default IItemStack[] web$getCondensedOutputs() { 14 | return (IItemStack[]) ((ICraftingPatternDetails) (Object) this).getCondensedOutputs(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AEGridMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import net.minecraft.util.IChatComponent; 4 | import net.minecraft.world.World; 5 | import net.minecraft.world.WorldServer; 6 | import net.minecraftforge.common.util.FakePlayer; 7 | 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | 11 | import appeng.api.networking.IGridHost; 12 | import appeng.api.networking.IGridNode; 13 | import appeng.api.networking.IMachineSet; 14 | import appeng.api.networking.crafting.ICraftingGrid; 15 | import appeng.api.networking.pathing.IPathingGrid; 16 | import appeng.api.networking.security.IActionHost; 17 | import appeng.api.networking.security.ISecurityGrid; 18 | import appeng.api.networking.security.PlayerSource; 19 | import appeng.api.networking.storage.IStorageGrid; 20 | import appeng.me.Grid; 21 | import appeng.parts.reporting.AbstractPartTerminal; 22 | import cpw.mods.fml.common.FMLCommonHandler; 23 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 24 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 25 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 26 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 27 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 28 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 29 | 30 | @Mixin(value = Grid.class, remap = false) 31 | public abstract class AEGridMixin implements IAEGrid { 32 | 33 | @Override 34 | public IAECraftingGrid web$getCraftingGrid() { 35 | return ((Grid) (Object) this).getCache(ICraftingGrid.class); 36 | } 37 | 38 | @Override 39 | public IAEPathingGrid web$getPathingGrid() { 40 | return ((Grid) (Object) this).getCache(IPathingGrid.class); 41 | } 42 | 43 | @Override 44 | public IAEStorageGrid web$getStorageGrid() { 45 | return ((Grid) (Object) this).getCache(IStorageGrid.class); 46 | } 47 | 48 | @Override 49 | public IAESecurityGrid web$getSecurityGrid() { 50 | return ((Grid) (Object) this).getCache(ISecurityGrid.class); 51 | } 52 | 53 | @Override 54 | public boolean web$isEmpty() { 55 | return ((Grid) (Object) this).isEmpty(); 56 | } 57 | 58 | @Unique 59 | private Class web$lastUsedMachineClass = null; 60 | 61 | @Unique 62 | public IChatComponent web$lastFakePlayerChatMessage; 63 | 64 | @Unique 65 | private PlayerSource web$cachedPlayerSource = null; 66 | 67 | @Override 68 | public Object web$getPlayerSource() { 69 | Grid internalGrid = (Grid) (Object) this; 70 | IMachineSet terminals = null; 71 | if (web$lastUsedMachineClass != null) terminals = internalGrid.getMachines(web$lastUsedMachineClass); 72 | if (web$lastUsedMachineClass == null || terminals.isEmpty()) { 73 | web$lastUsedMachineClass = null; 74 | Iterable> machines = internalGrid.getMachineClasses(); 75 | for (Class machine : machines) { 76 | if (AbstractPartTerminal.class.isAssignableFrom(machine) 77 | && !(terminals = internalGrid.getMachines(machine)).isEmpty()) { 78 | web$lastUsedMachineClass = machine; 79 | break; 80 | } 81 | } 82 | } 83 | IActionHost actionHost; 84 | World world; 85 | if (web$lastUsedMachineClass == null || terminals.isEmpty()) { 86 | // throw new RuntimeException("There is no terminal in the AE system"); 87 | actionHost = null; 88 | world = FMLCommonHandler.instance() 89 | .getMinecraftServerInstance() 90 | .worldServerForDimension(0); 91 | } else { 92 | IGridNode node = terminals.iterator() 93 | .next(); 94 | actionHost = (IActionHost) node.getMachine(); 95 | world = node.getWorld(); 96 | } 97 | 98 | if (web$cachedPlayerSource != null) { 99 | if (web$cachedPlayerSource.via != actionHost) web$cachedPlayerSource = null; 100 | else return web$cachedPlayerSource; 101 | } 102 | 103 | web$cachedPlayerSource = new PlayerSource( 104 | new FakePlayer((WorldServer) world, AE2Controller.AEControllerProfile) { 105 | 106 | @Override 107 | public void addChatMessage(IChatComponent message) { 108 | web$lastFakePlayerChatMessage = message; 109 | } 110 | }, 111 | actionHost); 112 | 113 | return web$cachedPlayerSource; 114 | } 115 | 116 | @Override 117 | public IChatComponent web$getLastFakePlayerChatMessage() { 118 | return web$lastFakePlayerChatMessage; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AEItemListMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.storage.data.IAEStack; 6 | import appeng.api.storage.data.IItemContainer; 7 | import appeng.api.storage.data.IItemList; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 9 | 10 | @Mixin(value = IItemList.class, remap = false) 11 | public interface AEItemListMixin 12 | extends IItemContainer, pl.kuba6000.ae2webintegration.core.interfaces.IItemList { 13 | 14 | @Override 15 | default IItemStack web$findPrecise(IItemStack stack) { 16 | return (IItemStack) findPrecise((StackType) stack); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AEItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.storage.data.IAEItemStack; 6 | import cpw.mods.fml.common.registry.GameRegistry; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 8 | 9 | @Mixin(IAEItemStack.class) 10 | public interface AEItemStackMixin extends IAEItemStack, IItemStack { 11 | 12 | @Override 13 | public default String web$getItemID() { 14 | return GameRegistry.findUniqueIdentifierFor(getItem()) 15 | .toString() + ":" 16 | + getItemDamage(); 17 | } 18 | 19 | @Override 20 | public default String web$getDisplayName() { 21 | return getItemStack().getDisplayName(); 22 | } 23 | 24 | @Override 25 | public default long web$getStackSize() { 26 | return getStackSize(); 27 | } 28 | 29 | @Override 30 | public default boolean web$isCraftable() { 31 | return isCraftable(); 32 | } 33 | 34 | @Override 35 | public default long web$getCountRequestable() { 36 | return getCountRequestable(); 37 | } 38 | 39 | @Override 40 | public default long web$getCountRequestableCrafts() { 41 | return getCountRequestableCrafts(); 42 | } 43 | 44 | @Override 45 | public default void web$reset() { 46 | reset(); 47 | } 48 | 49 | @Override 50 | public default boolean web$isSameType(IItemStack other) { 51 | return isSameType((IAEItemStack) other); 52 | } 53 | 54 | @Override 55 | public default IItemStack web$copy() { 56 | return (IItemStack) copy(); 57 | } 58 | 59 | @Override 60 | public default void web$setStackSize(long size) { 61 | setStackSize(size); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AEMeInventoryItemMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.config.Actionable; 6 | import appeng.api.networking.security.BaseActionSource; 7 | import appeng.api.storage.IMEInventory; 8 | import appeng.api.storage.data.IAEStack; 9 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEActionable; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEMeInventoryItem; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 13 | 14 | @Mixin(value = IMEInventory.class) 15 | public interface AEMeInventoryItemMixin extends IAEMeInventoryItem { 16 | 17 | @Override 18 | public default IItemStack web$extractItems(IItemStack stack, AEActionable mode, IAEGrid grid) { 19 | return (IItemStack) ((IMEInventory) (Object) this).extractItems( 20 | (IAEStack) stack, 21 | mode == AEActionable.MODULATE ? Actionable.MODULATE : Actionable.SIMULATE, 22 | (BaseActionSource) grid.web$getPlayerSource()); 23 | } 24 | 25 | @Override 26 | public default IItemStack web$getAvailableItem(IItemStack stack) { 27 | return (IItemStack) ((IMEInventory) (Object) this).getAvailableItem((IAEStack) stack); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/AEPlayerDataMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import java.util.UUID; 4 | 5 | import javax.annotation.Nonnull; 6 | 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | 11 | import com.google.common.base.Optional; 12 | import com.mojang.authlib.GameProfile; 13 | 14 | import appeng.core.worlddata.IWorldPlayerMapping; 15 | import cpw.mods.fml.common.FMLCommonHandler; 16 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEPlayerData; 17 | 18 | @Mixin(targets = "appeng.core.worlddata.PlayerData", remap = false) 19 | public class AEPlayerDataMixin implements IAEPlayerData { 20 | 21 | @Shadow 22 | @Final 23 | private IWorldPlayerMapping playerMapping; 24 | 25 | @Shadow 26 | public int getPlayerID(@Nonnull final GameProfile profile) { 27 | throw new UnsupportedOperationException("Mixin failed to apply."); 28 | } 29 | 30 | @Override 31 | public GameProfile web$getPlayerProfile(int playerId) { 32 | Optional maybe = playerMapping.get(playerId); 33 | if (!maybe.isPresent()) return null; 34 | UUID uuid = maybe.get(); 35 | // for (final EntityPlayer player : CommonHelper.proxy.getPlayers()) { 36 | // if (player.getUniqueID().equals(uuid)) { 37 | // return player.getGameProfile(); 38 | // } 39 | // } 40 | GameProfile p = FMLCommonHandler.instance() 41 | .getMinecraftServerInstance() 42 | .func_152358_ax() 43 | .func_152652_a(uuid); 44 | if (p == null) { 45 | p = new GameProfile(uuid, uuid.toString()); 46 | } 47 | return p; 48 | } 49 | 50 | @Override 51 | public int web$getPlayerId(GameProfile id) { 52 | return getPlayerID(id); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/PatternProviderViewableMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.util.DimensionalCoord; 6 | import appeng.api.util.IInterfaceViewable; 7 | import pl.kuba6000.ae2webintegration.core.api.DimensionalCoords; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IPatternProviderViewable; 9 | 10 | @Mixin(value = IInterfaceViewable.class) 11 | public interface PatternProviderViewableMixin extends IPatternProviderViewable { 12 | 13 | @Override 14 | public default String web$getName() { 15 | return ((IInterfaceViewable) (Object) this).getName(); 16 | } 17 | 18 | @Override 19 | public default DimensionalCoords web$getLocation() { 20 | DimensionalCoord coord = ((IInterfaceViewable) (Object) this).getLocation(); 21 | return new DimensionalCoords(coord.getDimension(), coord.x, coord.y, coord.z); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/service/AECraftingGridMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations.service; 2 | 3 | import java.util.LinkedHashSet; 4 | import java.util.Set; 5 | import java.util.concurrent.Future; 6 | 7 | import net.minecraft.util.IChatComponent; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | 11 | import com.google.common.collect.ImmutableSet; 12 | 13 | import appeng.api.networking.IGrid; 14 | import appeng.api.networking.crafting.ICraftingCPU; 15 | import appeng.api.networking.crafting.ICraftingGrid; 16 | import appeng.api.networking.crafting.ICraftingJob; 17 | import appeng.api.networking.crafting.ICraftingLink; 18 | import appeng.api.networking.security.BaseActionSource; 19 | import appeng.api.networking.security.PlayerSource; 20 | import appeng.api.storage.data.IAEItemStack; 21 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 22 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 23 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 24 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 25 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 26 | 27 | @Mixin(value = ICraftingGrid.class) 28 | public interface AECraftingGridMixin extends IAECraftingGrid { 29 | 30 | @Override 31 | public default int web$getCPUCount() { 32 | return ((ICraftingGrid) (Object) this).getCpus() 33 | .size(); 34 | } 35 | 36 | @Override 37 | public default Set web$getCPUs() { 38 | final ImmutableSet aecpus = ((ICraftingGrid) (Object) this).getCpus(); 39 | final Set cpus = new LinkedHashSet<>(aecpus.size()); 40 | int i = 1; 41 | for (ICraftingCPU cpu : aecpus) { 42 | cpus.add((ICraftingCPUCluster) cpu); 43 | ((ICraftingCPUCluster) cpu).web$setInternalID(i++); 44 | } 45 | return cpus; 46 | } 47 | 48 | @Override 49 | public default Future web$beginCraftingJob(IAEGrid grid, IItemStack stack) { 50 | PlayerSource actionSrc = (PlayerSource) grid.web$getPlayerSource(); 51 | final Future job = ((ICraftingGrid) (Object) this) 52 | .beginCraftingJob(actionSrc.player.worldObj, (IGrid) grid, actionSrc, (IAEItemStack) stack, null); 53 | return (Future) (Object) job; 54 | } 55 | 56 | @Override 57 | public default IChatComponent web$submitJob(IAECraftingJob job, ICraftingCPUCluster target, boolean prioritizePower, 58 | IAEGrid grid) { 59 | ICraftingLink link = ((ICraftingGrid) (Object) this).submitJob( 60 | (ICraftingJob) job, 61 | null, 62 | (ICraftingCPU) target, 63 | prioritizePower, 64 | (BaseActionSource) grid.web$getPlayerSource()); 65 | if (link != null) return null; 66 | return grid.web$getLastFakePlayerChatMessage(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/service/AEPathingGridMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations.service; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.networking.pathing.IPathingGrid; 6 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEControllerState; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 8 | 9 | @Mixin(value = IPathingGrid.class, remap = false) 10 | public interface AEPathingGridMixin extends IAEPathingGrid { 11 | 12 | @Override 13 | public default boolean web$isNetworkBooting() { 14 | return ((IPathingGrid) (Object) this).isNetworkBooting(); 15 | } 16 | 17 | @Override 18 | public default AEControllerState web$getControllerState() { 19 | return switch (((IPathingGrid) (Object) this).getControllerState()) { 20 | case CONTROLLER_CONFLICT -> AEControllerState.CONTROLLER_CONFLICT; 21 | case CONTROLLER_ONLINE -> AEControllerState.CONTROLLER_ONLINE; 22 | case NO_CONTROLLER -> AEControllerState.NO_CONTROLLER; 23 | default -> AEControllerState.UNSUPPORTED; 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/service/AESecurityGridMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations.service; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashMap; 5 | 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | 10 | import com.mojang.authlib.GameProfile; 11 | 12 | import appeng.api.config.SecurityPermissions; 13 | import appeng.core.worlddata.WorldData; 14 | import appeng.me.cache.SecurityCache; 15 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEPlayerData; 16 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 17 | 18 | @Mixin(value = SecurityCache.class, remap = false) 19 | public class AESecurityGridMixin implements IAESecurityGrid { 20 | 21 | @Shadow 22 | @Final 23 | private HashMap> playerPerms; 24 | 25 | @Override 26 | public boolean web$isAvailable() { 27 | return ((SecurityCache) (Object) this).isAvailable(); 28 | } 29 | 30 | @Override 31 | public long web$getSecurityKey() { 32 | return ((SecurityCache) (Object) this).getSecurityKey(); 33 | } 34 | 35 | @Override 36 | public int web$getOwner() { 37 | return ((SecurityCache) (Object) this).getOwner(); 38 | } 39 | 40 | @Override 41 | public GameProfile web$getOwnerProfile() { 42 | IAEPlayerData playerData = (IAEPlayerData) WorldData.instance() 43 | .playerData(); 44 | return playerData.web$getPlayerProfile(web$getOwner()); 45 | } 46 | 47 | @Override 48 | public boolean web$hasPermissions(int playerId) { 49 | if (web$getOwner() == playerId) return true; 50 | EnumSet permissions = playerPerms.get(playerId); 51 | if (permissions == null) { 52 | return false; 53 | } 54 | return permissions.containsAll( 55 | EnumSet.of( 56 | SecurityPermissions.BUILD, 57 | SecurityPermissions.EXTRACT, 58 | SecurityPermissions.INJECT, 59 | SecurityPermissions.CRAFT)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/AE2/implementations/service/AEStorageGridMixin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins.AE2.implementations.service; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | 5 | import appeng.api.networking.storage.IStorageGrid; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEMeInventoryItem; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 9 | 10 | @Mixin(value = IStorageGrid.class) 11 | public interface AEStorageGridMixin extends IAEStorageGrid { 12 | 13 | @Override 14 | public default IItemList web$getItemStorageList() { 15 | return (IItemList) (Object) ((IStorageGrid) (Object) this).getItemInventory() 16 | .getStorageList(); 17 | } 18 | 19 | @Override 20 | public default IAEMeInventoryItem web$getItemInventory() { 21 | return (IAEMeInventoryItem) ((IStorageGrid) (Object) this).getItemInventory(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/ae2interface/mixins/MixinPlugin.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.ae2interface.mixins; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | import org.spongepowered.asm.lib.tree.ClassNode; 11 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 12 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 13 | 14 | @SuppressWarnings("unused") 15 | public class MixinPlugin implements IMixinConfigPlugin { 16 | 17 | private static final Logger LOG = LogManager.getLogger("AE2WebIntegration mixins"); 18 | 19 | @Override 20 | public void onLoad(String mixinPackage) { 21 | 22 | } 23 | 24 | @Override 25 | public String getRefMapperConfig() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 31 | return false; 32 | } 33 | 34 | @Override 35 | public void acceptTargets(Set myTargets, Set otherTargets) { 36 | 37 | } 38 | 39 | @Override 40 | public List getMixins() { 41 | 42 | List mixins = new ArrayList<>( 43 | Arrays.asList( 44 | "AE2.CraftingGridCacheMixin", 45 | "AE2.CraftingCPUClusterMixin", 46 | "AE2.implementations.AEItemStackMixin", 47 | "AE2.implementations.AEItemListMixin", 48 | "AE2.implementations.AECraftingCPUClusterMixin", 49 | "AE2.implementations.AECraftingJobMixin", 50 | "AE2.implementations.AECraftingPatternDetailsMixin", 51 | "AE2.implementations.AEGridMixin", 52 | "AE2.implementations.AEMeInventoryItemMixin", 53 | "AE2.implementations.AEPlayerDataMixin", 54 | "AE2.implementations.PatternProviderViewableMixin", 55 | "AE2.implementations.service.AECraftingGridMixin", 56 | "AE2.implementations.service.AEPathingGridMixin", 57 | "AE2.implementations.service.AEStorageGridMixin", 58 | "AE2.implementations.service.AESecurityGridMixin")); 59 | 60 | LOG.info("MIXING INTO AE2 LETS GOOOOOOOOOOOOOOOOOOOOOOOOO"); 61 | 62 | return mixins; 63 | } 64 | 65 | @Override 66 | public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) { 67 | 68 | } 69 | 70 | @Override 71 | public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) { 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/AE2WebIntegration.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import static pl.kuba6000.ae2webintegration.core.AE2WebIntegration.MODID; 4 | 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import cpw.mods.fml.common.Mod; 9 | import cpw.mods.fml.common.SidedProxy; 10 | import cpw.mods.fml.common.event.FMLInitializationEvent; 11 | import cpw.mods.fml.common.event.FMLPostInitializationEvent; 12 | import cpw.mods.fml.common.event.FMLPreInitializationEvent; 13 | import cpw.mods.fml.common.event.FMLServerStartedEvent; 14 | import cpw.mods.fml.common.event.FMLServerStartingEvent; 15 | import cpw.mods.fml.common.event.FMLServerStoppingEvent; 16 | import pl.kuba6000.ae2webintegration.Tags; 17 | 18 | @Mod( 19 | modid = MODID, 20 | version = Tags.VERSION, 21 | name = "AE2WebIntegration-Core", 22 | acceptedMinecraftVersions = "*", 23 | acceptableRemoteVersions = "*") 24 | public class AE2WebIntegration { 25 | 26 | public static final String MODID = "ae2webintegration-core"; 27 | public static final Logger LOG = LogManager.getLogger(MODID); 28 | 29 | @SidedProxy( 30 | clientSide = "pl.kuba6000.ae2webintegration.core.ClientProxy", 31 | serverSide = "pl.kuba6000.ae2webintegration.core.CommonProxy") 32 | public static CommonProxy proxy; 33 | 34 | @Mod.EventHandler 35 | public void preInit(FMLPreInitializationEvent event) { 36 | proxy.preInit(event); 37 | } 38 | 39 | @Mod.EventHandler 40 | public void init(FMLInitializationEvent event) { 41 | proxy.init(event); 42 | } 43 | 44 | @Mod.EventHandler 45 | public void postInit(FMLPostInitializationEvent event) { 46 | proxy.postInit(event); 47 | } 48 | 49 | @Mod.EventHandler 50 | public void serverStarting(FMLServerStartingEvent event) { 51 | proxy.serverStarting(event); 52 | } 53 | 54 | @Mod.EventHandler 55 | public void serverStarted(FMLServerStartedEvent event) { 56 | proxy.serverStarted(event); 57 | } 58 | 59 | @Mod.EventHandler 60 | public void serverStarted(FMLServerStoppingEvent event) { 61 | proxy.serverStopping(event); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/AEMixinCallbacks.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import pl.kuba6000.ae2webintegration.core.api.IAEMixinCallbacks; 4 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingPatternDetails; 5 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IPatternProviderViewable; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 10 | 11 | public class AEMixinCallbacks implements IAEMixinCallbacks { 12 | 13 | public static AEMixinCallbacks INSTANCE = new AEMixinCallbacks(); 14 | 15 | @Override 16 | public void jobStarted(ICraftingCPUCluster cpuCluster, IAECraftingGrid cache, IAEGrid grid, boolean isMerging, 17 | boolean isAuthorPlayer) { 18 | if (!Config.TRACKING_TRACK_MACHINE_CRAFTING && !isAuthorPlayer) { 19 | return; 20 | } 21 | AE2JobTracker.addJob(cpuCluster, cache, grid, isMerging); 22 | } 23 | 24 | @Override 25 | public void craftingStatusPostedUpdate(ICraftingCPUCluster cpu, IItemStack diff) { 26 | AE2JobTracker.updateCraftingStatus(cpu, diff); 27 | } 28 | 29 | @Override 30 | public void pushedPattern(ICraftingCPUCluster cpu, IPatternProviderViewable provider, 31 | IAECraftingPatternDetails details) { 32 | AE2JobTracker.pushedPattern(cpu, provider, details); 33 | } 34 | 35 | @Override 36 | public void jobCompleted(IAEGrid grid, ICraftingCPUCluster cpu) { 37 | AE2JobTracker.completeCrafting(grid, cpu); 38 | } 39 | 40 | @Override 41 | public void jobCancelled(IAEGrid grid, ICraftingCPUCluster cpu) { 42 | AE2JobTracker.cancelCrafting(grid, cpu); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/AEWebAPI.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | 5 | import pl.kuba6000.ae2webintegration.core.api.IAEWebInterface; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.IAE; 7 | 8 | public class AEWebAPI implements IAEWebInterface { 9 | 10 | public static final AEWebAPI INSTANCE = new AEWebAPI(); 11 | 12 | @Override 13 | public GameProfile getAEWebGameProfile() { 14 | return AE2Controller.AEControllerProfile; 15 | } 16 | 17 | @Override 18 | public void initAEInterface(IAE ae) { 19 | AE2Controller.AE2Interface = ae; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ClientProxy.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | public class ClientProxy extends CommonProxy { 4 | 5 | // Override CommonProxy methods here, if you want a different behaviour on the client (e.g. registering renders). 6 | // Don't forget to call the super methods as well. 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/CommonProxy.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import cpw.mods.fml.common.FMLCommonHandler; 4 | import cpw.mods.fml.common.event.FMLInitializationEvent; 5 | import cpw.mods.fml.common.event.FMLPostInitializationEvent; 6 | import cpw.mods.fml.common.event.FMLPreInitializationEvent; 7 | import cpw.mods.fml.common.event.FMLServerStartedEvent; 8 | import cpw.mods.fml.common.event.FMLServerStartingEvent; 9 | import cpw.mods.fml.common.event.FMLServerStoppingEvent; 10 | import pl.kuba6000.ae2webintegration.Tags; 11 | import pl.kuba6000.ae2webintegration.core.commands.BaseCommandHandler; 12 | import pl.kuba6000.ae2webintegration.core.discord.DiscordManager; 13 | import pl.kuba6000.ae2webintegration.core.utils.VersionChecker; 14 | 15 | public class CommonProxy { 16 | 17 | // preInit "Run before anything else. Read your config, create blocks, items, etc, and register them with the 18 | // GameRegistry." (Remove if not needed) 19 | public void preInit(FMLPreInitializationEvent event) { 20 | Config.init(event.getModConfigurationDirectory()); 21 | Config.synchronizeConfiguration(); 22 | WebData.loadData(); 23 | GridData.loadData(); 24 | 25 | AE2WebIntegration.LOG.info("AE2WebIntegration loading at version " + Tags.VERSION); 26 | if (VersionChecker.isOutdated()) AE2WebIntegration.LOG.warn( 27 | "You are not on latest version ! Consider updating to {} at https://github.com/kuba6000/AE2-Web-Integration/releases/latest", 28 | VersionChecker.getLatestTag()); 29 | 30 | FMLCommonHandler.instance() 31 | .bus() 32 | .register(new FMLEventHandler()); 33 | } 34 | 35 | // load "Do your mod setup. Build whatever data structures you care about. Register recipes." (Remove if not needed) 36 | public void init(FMLInitializationEvent event) {} 37 | 38 | // postInit "Handle interaction with other mods, complete your setup based on this." (Remove if not needed) 39 | public void postInit(FMLPostInitializationEvent event) { 40 | 41 | } 42 | 43 | // register server commands in this event handler (Remove if not needed) 44 | public void serverStarting(FMLServerStartingEvent event) { 45 | event.registerServerCommand(new BaseCommandHandler()); 46 | } 47 | 48 | public void serverStarted(FMLServerStartedEvent event) { 49 | AE2Controller.init(); 50 | DiscordManager.init(); 51 | if (!Config.AE_PUBLIC_MODE && !Config.DISCORD_WEBHOOK.isEmpty()) { 52 | DiscordManager.postMessageNonBlocking( 53 | new DiscordManager.DiscordEmbed("AE2 Web Integration", "Discord integration started!")); 54 | } else if (Config.AE_PUBLIC_MODE && !Config.DISCORD_WEBHOOK.isEmpty()) { 55 | DiscordManager.postMessageNonBlocking( 56 | new DiscordManager.DiscordEmbed( 57 | "AE2 Web Integration", 58 | "Warning!\nDiscord integration webhook is set in the config, but the public mode is enabled!\nDiscord integration will be disabled!", 59 | 15548997)); 60 | } 61 | } 62 | 63 | public void serverStopping(FMLServerStoppingEvent event) { 64 | AE2Controller.stopHTTPServer(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/Config.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import java.io.File; 4 | import java.util.Random; 5 | 6 | import net.minecraftforge.common.config.Configuration; 7 | 8 | public class Config { 9 | 10 | private static File configDirectory; 11 | private static File configFile; 12 | 13 | public static String AE_PASSWORD = new Random().ints(48, 122 + 1) 14 | .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) 15 | .limit(16) 16 | .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) 17 | .toString(); 18 | public static int AE_PORT = 2324; 19 | public static boolean ALLOW_NO_PASSWORD_ON_LOCALHOST = true; 20 | public static boolean AE_PUBLIC_MODE = true; 21 | public static int AE_MAX_REQUESTS_BEFORE_LOGGED_IN_PER_MINUTE = 20; 22 | 23 | // discord 24 | public static String DISCORD_WEBHOOK = ""; 25 | public static String DISCORD_ROLE_ID = ""; 26 | 27 | // tracking 28 | // TODO: Add more customization options (order time, size, item type ? etc.) 29 | public static boolean TRACKING_TRACK_MACHINE_CRAFTING = false; 30 | 31 | public static void synchronizeConfiguration() { 32 | Configuration configuration = new Configuration(configFile); 33 | AE_PORT = configuration 34 | .getInt("port", Configuration.CATEGORY_GENERAL, AE_PORT, 1, 65535, "Port for the hosted website"); 35 | AE_PASSWORD = configuration 36 | .getString("password", Configuration.CATEGORY_GENERAL, AE_PASSWORD, "Password for the admin account"); 37 | ALLOW_NO_PASSWORD_ON_LOCALHOST = configuration.getBoolean( 38 | "allow_no_password_on_localhost", 39 | Configuration.CATEGORY_GENERAL, 40 | ALLOW_NO_PASSWORD_ON_LOCALHOST, 41 | "Don't require to login using loopback address (127.0.0.1/localhost)"); 42 | AE_PUBLIC_MODE = configuration.getBoolean( 43 | "public_mode", 44 | Configuration.CATEGORY_GENERAL, 45 | AE_PUBLIC_MODE, 46 | "Public server mode = enable registration system on the website, players will be able to register and login to monitor their own ae networks, " 47 | + "if disabled, there is only one admin account with password set in the config file with access to all networks on the server"); 48 | AE_MAX_REQUESTS_BEFORE_LOGGED_IN_PER_MINUTE = configuration.getInt( 49 | "max_requests_before_logged_in_per_minute", 50 | Configuration.CATEGORY_GENERAL, 51 | AE_MAX_REQUESTS_BEFORE_LOGGED_IN_PER_MINUTE, 52 | 1, 53 | 1000, 54 | "How many requests can be made before user is logged in per minute"); 55 | 56 | DISCORD_WEBHOOK = configuration.getString( 57 | "discord_webhook", 58 | "discord", 59 | "", 60 | "Discord webhook url (OPTIONAL, leave empty to ignore) (WORKS ONLY IF PUBLIC_MODE IS DISABLED)"); 61 | DISCORD_ROLE_ID = configuration 62 | .getString("discord_role_id", "discord", "", "Role to ping on message (OPTIONAL, leave empty to ignore)"); 63 | 64 | TRACKING_TRACK_MACHINE_CRAFTING = configuration.getBoolean( 65 | "track_machine_crafting", 66 | "tracking", 67 | TRACKING_TRACK_MACHINE_CRAFTING, 68 | "Track automated crafting jobs (not ordered by player)"); 69 | 70 | if (configuration.hasKey(Configuration.CATEGORY_GENERAL, "cpu_count_threshold")) { 71 | configuration.getInt( 72 | "cpu_count_threshold", 73 | Configuration.CATEGORY_GENERAL, 74 | 0, 75 | 0, 76 | 0, 77 | "[DEPRECATED] This option is no longer used, you can remove it from your config file."); 78 | } 79 | 80 | if (configuration.hasChanged()) { 81 | configuration.save(); 82 | } 83 | } 84 | 85 | public static void init(File configDirectory) { 86 | Config.configDirectory = new File(configDirectory, "ae2webintegration"); 87 | Config.configFile = new File(Config.configDirectory, "ae2webintegration.cfg"); 88 | if (!Config.configDirectory.exists()) { 89 | Config.configDirectory.mkdirs(); 90 | File oldConfigFile = new File(configDirectory, "ae2webintegration.cfg"); 91 | if (oldConfigFile.exists()) { 92 | oldConfigFile.renameTo(Config.configFile); 93 | } 94 | } 95 | 96 | } 97 | 98 | public static File getConfigFile(String fileName) { 99 | return new File(configDirectory, fileName); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/FMLEventHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import net.minecraft.entity.player.EntityPlayerMP; 4 | import net.minecraft.util.ChatComponentText; 5 | import net.minecraft.util.EnumChatFormatting; 6 | 7 | import cpw.mods.fml.common.eventhandler.SubscribeEvent; 8 | import cpw.mods.fml.common.gameevent.PlayerEvent; 9 | import cpw.mods.fml.common.gameevent.TickEvent; 10 | import pl.kuba6000.ae2webintegration.core.ae2request.sync.ISyncedRequest; 11 | import pl.kuba6000.ae2webintegration.core.utils.VersionChecker; 12 | 13 | public class FMLEventHandler { 14 | 15 | @SubscribeEvent 16 | public void tick(TickEvent.ServerTickEvent event) { 17 | if (event.phase == TickEvent.Phase.START) return; 18 | ++AE2Controller.timer; 19 | if (AE2Controller.timer % 5 == 0) { 20 | while (AE2Controller.requests.peek() != null) { 21 | ISyncedRequest request = AE2Controller.requests.poll(); 22 | request.handle(AE2Controller.AE2Interface); 23 | } 24 | } 25 | } 26 | 27 | @SubscribeEvent 28 | public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { 29 | if (!(event.player instanceof EntityPlayerMP)) return; 30 | if (VersionChecker.isOutdated() && event.player.canCommandSenderUseCommand(4, "seed")) 31 | event.player.addChatMessage( 32 | new ChatComponentText( 33 | EnumChatFormatting.GREEN.toString() + EnumChatFormatting.BOLD 34 | + "----> AE2WebIntegration -> New version detected! Consider updating at https://github.com/kuba6000/AE2-Web-Integration/releases/latest")); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/GridData.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import static pl.kuba6000.ae2webintegration.core.AE2WebIntegration.LOG; 4 | 5 | import java.io.File; 6 | import java.io.Reader; 7 | import java.io.Writer; 8 | import java.lang.reflect.Type; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.HashMap; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.concurrent.Future; 13 | 14 | import com.google.common.io.Files; 15 | import com.google.gson.Gson; 16 | import com.google.gson.reflect.TypeToken; 17 | 18 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEControllerState; 19 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 20 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 21 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 22 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 23 | import pl.kuba6000.ae2webintegration.core.utils.GSONUtils; 24 | 25 | public class GridData { 26 | 27 | @GSONUtils.SkipGSON 28 | private static final File dataFile = Config.getConfigFile("griddata.json"); 29 | 30 | @GSONUtils.SkipGSON 31 | private static ConcurrentHashMap gridDataMap = new ConcurrentHashMap<>(); 32 | 33 | public boolean isTracked = false; 34 | 35 | @GSONUtils.SkipGSON 36 | public AE2JobTracker trackingInfo = new AE2JobTracker(); 37 | 38 | @GSONUtils.SkipGSON 39 | private int nextJobID = 1; 40 | 41 | private int getNextJobID() { 42 | return nextJobID++; 43 | } 44 | 45 | @GSONUtils.SkipGSON 46 | public HashMap> jobs = new HashMap<>(); 47 | 48 | public int addJob(Future job) { 49 | int jobID = getNextJobID(); 50 | jobs.put(jobID, job); 51 | return jobID; 52 | } 53 | 54 | public static GridData get(long gridKey) { 55 | return gridDataMap.computeIfAbsent(gridKey, k -> new GridData()); 56 | } 57 | 58 | public static GridData get(IAEGrid grid) { 59 | IAEPathingGrid pathing = grid.web$getPathingGrid(); 60 | if (pathing == null || pathing.web$isNetworkBooting() 61 | || pathing.web$getControllerState() != AEControllerState.CONTROLLER_ONLINE) { 62 | return null; 63 | } 64 | IAESecurityGrid security = grid.web$getSecurityGrid(); 65 | if (security == null || !security.web$isAvailable()) { 66 | return null; 67 | } 68 | long gridKey = security.web$getSecurityKey(); 69 | if (gridKey == -1) { 70 | return null; 71 | } 72 | return gridDataMap.computeIfAbsent(gridKey, k -> new GridData()); 73 | } 74 | 75 | public static void saveChanges() { 76 | Gson gson = GSONUtils.GSON_BUILDER.create(); 77 | Writer writer = null; 78 | try { 79 | writer = Files.newWriter(dataFile, StandardCharsets.UTF_8); 80 | gson.toJson(gridDataMap, writer); 81 | writer.flush(); 82 | writer.close(); 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | } finally { 86 | if (writer != null) try { 87 | writer.close(); 88 | } catch (Exception ignored) {} 89 | } 90 | } 91 | 92 | public static void loadData() { 93 | Gson gson = GSONUtils.GSON_BUILDER.create(); 94 | if (!dataFile.exists()) { 95 | LOG.info("Grid data file not found, creating a new one."); 96 | saveChanges(); 97 | return; 98 | } 99 | Reader reader = null; 100 | try { 101 | reader = Files.newReader(dataFile, StandardCharsets.UTF_8); 102 | Type type = new TypeToken>() {}.getType(); 103 | gridDataMap = gson.fromJson(reader, type); 104 | } catch (Exception e) { 105 | LOG.error("Failed to load web data from file: {}", dataFile.getAbsolutePath(), e); 106 | gridDataMap.clear(); 107 | saveChanges(); 108 | } finally { 109 | if (reader != null) try { 110 | reader.close(); 111 | } catch (Exception ignored) {} 112 | } 113 | 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/PasswordHelper.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import java.math.BigInteger; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.security.SecureRandom; 6 | import java.security.spec.InvalidKeySpecException; 7 | 8 | import javax.crypto.SecretKeyFactory; 9 | import javax.crypto.spec.PBEKeySpec; 10 | 11 | public class PasswordHelper { 12 | 13 | private static final int ITERATIONS = 65536; 14 | private static final int HASH_LENGTH = 512; // Length of the hash in bytes 15 | 16 | public static String generateStrongPasswordHash(String password) 17 | throws NoSuchAlgorithmException, InvalidKeySpecException { 18 | char[] chars = password.toCharArray(); 19 | byte[] salt = getSalt(); 20 | 21 | PBEKeySpec spec = new PBEKeySpec(chars, salt, ITERATIONS, HASH_LENGTH); 22 | SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 23 | 24 | byte[] hash = skf.generateSecret(spec) 25 | .getEncoded(); 26 | return ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash); 27 | } 28 | 29 | private static byte[] getSalt() throws NoSuchAlgorithmException { 30 | SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); 31 | byte[] salt = new byte[16]; 32 | sr.nextBytes(salt); 33 | return salt; 34 | } 35 | 36 | private static String toHex(byte[] array) throws NoSuchAlgorithmException { 37 | BigInteger bi = new BigInteger(1, array); 38 | String hex = bi.toString(16); 39 | 40 | int paddingLength = (array.length * 2) - hex.length(); 41 | if (paddingLength > 0) { 42 | return String.format("%0" + paddingLength + "d", 0) + hex; 43 | } else { 44 | return hex; 45 | } 46 | } 47 | 48 | public static boolean validatePassword(String originalPassword, String storedPassword) 49 | throws NoSuchAlgorithmException, InvalidKeySpecException { 50 | String[] parts = storedPassword.split(":"); 51 | int iterations = Integer.parseInt(parts[0]); 52 | 53 | byte[] salt = fromHex(parts[1]); 54 | byte[] hash = fromHex(parts[2]); 55 | 56 | PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), salt, iterations, hash.length * 8); 57 | SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 58 | byte[] testHash = skf.generateSecret(spec) 59 | .getEncoded(); 60 | 61 | int diff = hash.length ^ testHash.length; 62 | for (int i = 0; i < hash.length && i < testHash.length; i++) { 63 | diff |= hash[i] ^ testHash[i]; 64 | } 65 | return diff == 0; 66 | } 67 | 68 | private static byte[] fromHex(String hex) throws NoSuchAlgorithmException { 69 | byte[] bytes = new byte[hex.length() / 2]; 70 | for (int i = 0; i < bytes.length; i++) { 71 | bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); 72 | } 73 | return bytes; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/WebData.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core; 2 | 3 | import static pl.kuba6000.ae2webintegration.core.AE2WebIntegration.LOG; 4 | 5 | import java.io.File; 6 | import java.io.Reader; 7 | import java.io.Writer; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.HashMap; 10 | import java.util.UUID; 11 | 12 | import com.google.common.io.Files; 13 | import com.google.gson.Gson; 14 | import com.mojang.authlib.GameProfile; 15 | 16 | import cpw.mods.fml.common.FMLCommonHandler; 17 | import pl.kuba6000.ae2webintegration.core.utils.GSONUtils; 18 | 19 | public class WebData { 20 | 21 | static WebData instance = new WebData(); 22 | 23 | private static final File dataFile = Config.getConfigFile("webdata.json"); 24 | 25 | private HashMap UUIDToId = new HashMap<>(); 26 | private HashMap IdToUUID = new HashMap<>(); 27 | private HashMap passwords = new HashMap<>(); 28 | 29 | public static int getPlayerId(String name) { 30 | if (name == null || name.isEmpty()) { 31 | return -1; 32 | } 33 | GameProfile profile = FMLCommonHandler.instance() 34 | .getMinecraftServerInstance() 35 | .func_152358_ax() 36 | .func_152655_a(name); 37 | if (profile == null) { 38 | return -1; 39 | } 40 | Integer id = instance.UUIDToId.get(profile.getId()); 41 | if (id != null) { 42 | return id; 43 | } 44 | 45 | return -1; 46 | } 47 | 48 | public static boolean verifyPassword(int playerId, String password) { 49 | UUID id = instance.IdToUUID.get(playerId); 50 | if (id == null) { 51 | LOG.warn("Player ID {} not found in IdToUUID map.", playerId); 52 | return false; 53 | } 54 | if (instance.passwords.containsKey(id)) { 55 | try { 56 | return PasswordHelper.validatePassword(password, instance.passwords.get(id)); 57 | } catch (Exception e) { 58 | LOG.error("Password verification failed for player ID: {}", playerId, e); 59 | return false; 60 | } 61 | } 62 | 63 | return false; 64 | } 65 | 66 | public static void setPassword(GameProfile playerId, String passwordHash) { 67 | if (passwordHash == null || passwordHash.isEmpty()) { 68 | instance.passwords.remove(playerId.getId()); 69 | } else { 70 | try { 71 | instance.passwords.put(playerId.getId(), passwordHash); 72 | int pID = AE2Controller.AE2Interface.web$getPlayerData() 73 | .web$getPlayerId(playerId); 74 | instance.UUIDToId.put(playerId.getId(), pID); 75 | instance.IdToUUID.put(pID, playerId.getId()); 76 | } catch (Exception e) { 77 | LOG.error("Failed to set password for player ID: {}", playerId, e); 78 | } 79 | } 80 | saveChanges(); 81 | } 82 | 83 | private static void saveChanges() { 84 | Gson gson = GSONUtils.GSON_BUILDER.create(); 85 | Writer writer = null; 86 | try { 87 | writer = Files.newWriter(dataFile, StandardCharsets.UTF_8); 88 | gson.toJson(instance, writer); 89 | writer.flush(); 90 | writer.close(); 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } finally { 94 | if (writer != null) try { 95 | writer.close(); 96 | } catch (Exception ignored) {} 97 | } 98 | } 99 | 100 | public static void loadData() { 101 | Gson gson = GSONUtils.GSON_BUILDER.create(); 102 | if (!dataFile.exists()) { 103 | LOG.info("Web data file not found, creating a new one."); 104 | saveChanges(); 105 | return; 106 | } 107 | Reader reader = null; 108 | try { 109 | reader = Files.newReader(dataFile, StandardCharsets.UTF_8); 110 | instance = gson.fromJson(reader, WebData.class); 111 | } catch (Exception e) { 112 | LOG.error("Failed to load web data from file: {}", dataFile.getAbsolutePath(), e); 113 | instance.UUIDToId.clear(); 114 | instance.IdToUUID.clear(); 115 | instance.passwords.clear(); 116 | saveChanges(); 117 | } finally { 118 | if (reader != null) try { 119 | reader.close(); 120 | } catch (Exception ignored) {} 121 | } 122 | 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/IRequest.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | import com.google.gson.GsonBuilder; 6 | 7 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 8 | import pl.kuba6000.ae2webintegration.core.utils.GSONUtils; 9 | 10 | public abstract class IRequest { 11 | 12 | protected static GsonBuilder JSONBuilder = GSONUtils.GSON_BUILDER; 13 | 14 | private static class JSON_Structure { 15 | 16 | String status; 17 | Object data; 18 | } 19 | 20 | public AtomicBoolean isDone = new AtomicBoolean(false); 21 | protected String status = "TIMEOUT"; 22 | protected Object data = null; 23 | 24 | abstract public void handle(AE2Controller.RequestContext context); 25 | 26 | Object getData() { 27 | return data; 28 | } 29 | 30 | protected void setData(Object data) { 31 | this.data = data; 32 | } 33 | 34 | public String getJSON() { 35 | JSON_Structure structure = new JSON_Structure(); 36 | structure.status = status; 37 | structure.data = getData(); 38 | return JSONBuilder.create() 39 | .toJson(structure); 40 | } 41 | 42 | public void done() { 43 | this.status = "OK"; 44 | this.isDone.set(true); 45 | } 46 | 47 | public void deny(String status) { 48 | this.status = status; 49 | this.isDone.set(true); 50 | } 51 | 52 | public void noParam(String... params) { 53 | deny("NO_PARAM"); 54 | setData(params); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/async/GetTracking.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.async; 2 | 3 | import java.util.Map; 4 | 5 | import pl.kuba6000.ae2webintegration.core.AE2JobTracker; 6 | import pl.kuba6000.ae2webintegration.core.api.JSON_CompactedJobTrackingInfo; 7 | 8 | public class GetTracking extends IAsyncRequest { 9 | 10 | @Override 11 | public void handle(Map getParams) { 12 | if (grid == null) { 13 | deny("GRID_NOT_FOUND"); 14 | return; 15 | } 16 | if (!getParams.containsKey("id")) { 17 | noParam("id"); 18 | return; 19 | } 20 | int id = Integer.parseInt(getParams.get("id")); 21 | 22 | AE2JobTracker.JobTrackingInfo info = grid.trackingInfo.trackingInfos.get(id); 23 | if (info == null) { 24 | deny("TRACKING_NOT_FOUND"); 25 | return; 26 | } 27 | 28 | setData(new JSON_CompactedJobTrackingInfo(info)); 29 | done(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/async/GetTrackingHistory.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.async; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Map; 5 | 6 | import pl.kuba6000.ae2webintegration.core.AE2JobTracker; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 8 | 9 | public class GetTrackingHistory extends IAsyncRequest { 10 | 11 | private static class JSON_TrackingHistoryElement { 12 | 13 | public long timeStarted; 14 | public long timeDone; 15 | public boolean wasCancelled; 16 | public IItemStack finalOutput; 17 | public int id; 18 | } 19 | 20 | @Override 21 | public void handle(Map getParams) { 22 | if (grid == null) { 23 | deny("GRID_NOT_FOUND"); 24 | return; 25 | } 26 | ArrayList jobs = new ArrayList<>(grid.trackingInfo.trackingInfos.size()); 27 | 28 | for (Map.Entry integerJobTrackingInfoEntry : grid.trackingInfo.trackingInfos 29 | .entrySet()) { 30 | JSON_TrackingHistoryElement element = new JSON_TrackingHistoryElement(); 31 | element.id = integerJobTrackingInfoEntry.getKey(); 32 | element.timeStarted = integerJobTrackingInfoEntry.getValue().timeStarted; 33 | element.timeDone = integerJobTrackingInfoEntry.getValue().timeDone; 34 | element.wasCancelled = integerJobTrackingInfoEntry.getValue().wasCancelled; 35 | element.finalOutput = integerJobTrackingInfoEntry.getValue().finalOutput; 36 | jobs.add(element); 37 | } 38 | 39 | jobs.sort((i1, i2) -> Long.compare(i2.timeDone, i1.timeDone)); 40 | 41 | setData(jobs); 42 | done(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/async/GridSettings.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.async; 2 | 3 | import java.util.Map; 4 | 5 | import pl.kuba6000.ae2webintegration.core.GridData; 6 | 7 | public class GridSettings extends IAsyncRequest { 8 | 9 | @Override 10 | public void handle(Map getParams) { 11 | if (grid == null) { 12 | deny("GRID_NOT_FOUND"); 13 | return; 14 | } 15 | 16 | if (getParams.containsKey("track")) { 17 | grid.isTracked = getParams.get("track") 18 | .equals("1"); 19 | GridData.saveChanges(); 20 | } 21 | 22 | setData(grid); 23 | done(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/async/IAsyncRequest.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.async; 2 | 3 | import java.util.Map; 4 | 5 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 6 | import pl.kuba6000.ae2webintegration.core.GridData; 7 | import pl.kuba6000.ae2webintegration.core.ae2request.IRequest; 8 | 9 | public abstract class IAsyncRequest extends IRequest { 10 | 11 | protected AE2Controller.RequestContext context = null; 12 | protected long gridKey = -1; 13 | protected GridData grid = null; 14 | 15 | public void handle(Map getParams) {}; 16 | 17 | @Override 18 | public void handle(AE2Controller.RequestContext context) { 19 | this.context = context; 20 | String gridstr = context.getGetParams() 21 | .get("grid"); 22 | if (gridstr == null || gridstr.isEmpty()) gridKey = -1; 23 | else gridKey = Long.parseLong(gridstr); 24 | if (gridKey != -1) { 25 | grid = GridData.get(gridKey); 26 | } 27 | handle(context.getGetParams()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/CancelCPU.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.Map; 4 | 5 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 7 | 8 | public class CancelCPU extends ISyncedRequest { 9 | 10 | private String cpuName; 11 | 12 | @Override 13 | boolean init(Map getParams) { 14 | if (!getParams.containsKey("cpu")) { 15 | noParam("cpu"); 16 | return false; 17 | } 18 | cpuName = getParams.get("cpu"); 19 | return true; 20 | } 21 | 22 | @Override 23 | void handle(IAEGrid grid) { 24 | if (grid == null) { 25 | deny("GRID_NOT_FOUND"); 26 | return; 27 | } 28 | ICraftingCPUCluster cluster = GetCPUList.getCPUList(grid.web$getCraftingGrid()) 29 | .get(cpuName); 30 | if (cluster == null) { 31 | deny("CPU_NOT_FOUND"); 32 | return; 33 | } 34 | if (cluster.web$isBusy()) { 35 | cluster.web$cancel(); 36 | done(); 37 | return; 38 | } 39 | deny("CPU_NOT_BUSY"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/GetCPU.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 8 | import pl.kuba6000.ae2webintegration.core.AE2JobTracker; 9 | import pl.kuba6000.ae2webintegration.core.api.JSON_CompactedItem; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 13 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 14 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 15 | 16 | public class GetCPU extends ISyncedRequest { 17 | 18 | private static class JSON_ClusterData { 19 | 20 | public long size; 21 | public boolean isBusy; 22 | public IItemStack finalOutput; 23 | public ArrayList items; 24 | public boolean hasTrackingInfo = false; 25 | public long timeStarted = 0L; 26 | public long timeElapsed = 0L; 27 | } 28 | 29 | String cpuName = null; 30 | 31 | @Override 32 | boolean init(Map getParams) { 33 | if (!getParams.containsKey("cpu")) { 34 | noParam("cpu"); 35 | return false; 36 | } 37 | cpuName = getParams.get("cpu"); 38 | return true; 39 | } 40 | 41 | @Override 42 | void handle(IAEGrid grid) { 43 | if (grid == null) { 44 | deny("GRID_NOT_FOUND"); 45 | return; 46 | } 47 | IAECraftingGrid craftingGrid = grid.web$getCraftingGrid(); 48 | 49 | ICraftingCPUCluster cpu = GetCPUList.getCPUList(craftingGrid) 50 | .get(cpuName); 51 | if (cpu == null) { 52 | deny("CPU_NOT_FOUND"); 53 | return; 54 | } 55 | 56 | JSON_ClusterData clusterData = new JSON_ClusterData(); 57 | clusterData.size = cpu.web$getAvailableStorage(); 58 | clusterData.isBusy = cpu.web$isBusy(); 59 | if (clusterData.isBusy) { 60 | clusterData.finalOutput = cpu.web$getFinalOutput(); 61 | AE2JobTracker.JobTrackingInfo trackingInfo = AE2JobTracker.trackingInfoMap.get(cpu); 62 | clusterData.hasTrackingInfo = trackingInfo != null; 63 | 64 | HashMap prep = new HashMap<>(); 65 | IItemList items = AE2Controller.AE2Interface.web$createItemList(); 66 | cpu.web$getActiveItems(items); 67 | for (IItemStack itemStack : items) { 68 | JSON_CompactedItem compactedItem = JSON_CompactedItem.create(itemStack); 69 | prep.computeIfAbsent(compactedItem, k -> compactedItem).active += itemStack.web$getStackSize(); 70 | } 71 | items = AE2Controller.AE2Interface.web$createItemList(); 72 | cpu.web$getPendingItems(items); 73 | for (IItemStack itemStack : items) { 74 | JSON_CompactedItem compactedItem = JSON_CompactedItem.create(itemStack); 75 | prep.computeIfAbsent(compactedItem, k -> compactedItem).pending += itemStack.web$getStackSize(); 76 | } 77 | items = AE2Controller.AE2Interface.web$createItemList(); 78 | cpu.web$getStorageItems(items); 79 | for (IItemStack itemStack : items) { 80 | JSON_CompactedItem compactedItem = JSON_CompactedItem.create(itemStack); 81 | prep.computeIfAbsent(compactedItem, k -> compactedItem).stored += itemStack.web$getStackSize(); 82 | } 83 | 84 | if (clusterData.hasTrackingInfo) { 85 | clusterData.timeStarted = trackingInfo.timeStarted; 86 | clusterData.timeElapsed = (System.currentTimeMillis()) - clusterData.timeStarted; 87 | for (IItemStack stack : trackingInfo.timeSpentOn.keySet()) { 88 | JSON_CompactedItem compactedItem = JSON_CompactedItem.create(stack); 89 | JSON_CompactedItem finalCompactedItem = compactedItem; 90 | compactedItem = prep.computeIfAbsent(compactedItem, k -> finalCompactedItem); 91 | compactedItem.timeSpentCrafting += trackingInfo.getTimeSpentOn(stack); 92 | compactedItem.craftedTotal += trackingInfo.craftedTotal.getOrDefault(stack, 0L); 93 | compactedItem.shareInCraftingTime += trackingInfo.getShareInCraftingTime(stack); 94 | compactedItem.shareInCraftingTimeCombined = Math 95 | .min(((double) compactedItem.timeSpentCrafting) / (double) clusterData.timeElapsed, 1d); 96 | compactedItem.craftsPerSec = (double) compactedItem.craftedTotal 97 | / (compactedItem.timeSpentCrafting / 1000d); 98 | } 99 | } 100 | 101 | clusterData.items = new ArrayList<>(prep.values()); 102 | // TODO Move sorting to javascript! 103 | clusterData.items.sort((i1, i2) -> { 104 | if (i1.active > 0 && i2.active > 0) return Long.compare(i2.active, i1.active); 105 | else if (i1.active > 0 && i2.active == 0) return -1; 106 | else if (i1.active == 0 && i2.active > 0) return 1; 107 | if (i1.pending > 0 && i2.pending > 0) return Long.compare(i2.pending, i1.pending); 108 | else if (i1.pending > 0 && i2.pending == 0) return -1; 109 | else if (i1.pending == 0 && i2.pending > 0) return 1; 110 | return Long.compare(i2.stored, i1.stored); 111 | }); 112 | 113 | } 114 | 115 | setData(clusterData); 116 | done(); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/GetCPUList.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import pl.kuba6000.ae2webintegration.core.AE2JobTracker; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 11 | 12 | public class GetCPUList extends ISyncedRequest { 13 | 14 | private static class JSON_CpuInfo { 15 | 16 | public boolean isBusy; 17 | public IItemStack finalOutput; 18 | public long availableStorage; 19 | public long usedStorage; 20 | public long coProcessors; 21 | public boolean hasTrackingInfo = false; 22 | public long timeStarted = 0L; 23 | } 24 | 25 | public static Map getCPUList(IAECraftingGrid craftingGrid) { 26 | LinkedHashMap orderedMap = new LinkedHashMap<>(); 27 | for (ICraftingCPUCluster cpu : craftingGrid.web$getCPUs()) { 28 | String name = cpu.web$getName(); 29 | orderedMap.put(name, cpu); 30 | } 31 | return orderedMap; 32 | } 33 | 34 | @Override 35 | boolean init(Map getParams) { 36 | return true; 37 | } 38 | 39 | @Override 40 | void handle(IAEGrid grid) { 41 | if (grid == null) { 42 | deny("GRID_NOT_FOUND"); 43 | return; 44 | } 45 | Map clusters = getCPUList(grid.web$getCraftingGrid()); 46 | LinkedHashMap cpuList = new LinkedHashMap<>(clusters.size()); 47 | for (Map.Entry entry : clusters.entrySet()) { 48 | JSON_CpuInfo cpuInfo = new JSON_CpuInfo(); 49 | ICraftingCPUCluster cluster = entry.getValue(); 50 | cpuInfo.availableStorage = cluster.web$getAvailableStorage(); 51 | cpuInfo.usedStorage = cluster.web$getUsedStorage(); 52 | cpuInfo.coProcessors = cluster.web$getCoProcessors(); 53 | if (cpuInfo.isBusy = cluster.web$isBusy()) { 54 | cpuInfo.finalOutput = cluster.web$getFinalOutput(); 55 | AE2JobTracker.JobTrackingInfo trackingInfo = AE2JobTracker.trackingInfoMap.get(cluster); 56 | if (cpuInfo.hasTrackingInfo = trackingInfo != null) { 57 | cpuInfo.timeStarted = trackingInfo.timeStarted; 58 | } 59 | } 60 | cpuList.put(entry.getKey(), cpuInfo); 61 | } 62 | setData(cpuList); 63 | done(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/GetGridList.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.ArrayList; 4 | 5 | import com.mojang.authlib.GameProfile; 6 | 7 | import pl.kuba6000.ae2webintegration.core.GridData; 8 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEControllerState; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IAE; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 13 | 14 | public class GetGridList extends ISyncedRequest { 15 | 16 | private static class JSON_GridData { 17 | 18 | JSON_GridData(long key, int cpuCount, String owner, boolean isOwned, boolean isTrackingEnabled) { 19 | this.key = key; 20 | this.cpuCount = cpuCount; 21 | this.owner = owner; 22 | this.isOwned = isOwned; 23 | this.isTrackingEnabled = isTrackingEnabled; 24 | } 25 | 26 | public long key; // key == -1 -> not attachable 27 | public int cpuCount; 28 | public String owner; 29 | public boolean isOwned; 30 | public boolean isTrackingEnabled = false; 31 | } 32 | 33 | @Override 34 | public void handle(IAE ae) { 35 | ArrayList grids = new ArrayList<>(); 36 | for (IAEGrid grid : ae.web$getGrids()) { 37 | IAEPathingGrid pathing = grid.web$getPathingGrid(); 38 | if (pathing == null || pathing.web$isNetworkBooting() 39 | || pathing.web$getControllerState() != AEControllerState.CONTROLLER_ONLINE) { 40 | continue; 41 | } 42 | IAESecurityGrid security = grid.web$getSecurityGrid(); 43 | if (security == null || !security.web$isAvailable() || security.web$getSecurityKey() == -1) { 44 | if (context.isAdmin()) { 45 | grids.add( 46 | new JSON_GridData( 47 | -1, 48 | grid.web$getCraftingGrid() 49 | .web$getCPUCount(), 50 | "N/A", 51 | false, 52 | false)); 53 | } 54 | continue; 55 | } 56 | if (!context.isAdmin() && !security.web$hasPermissions(context.getUserID())) { 57 | continue; 58 | } 59 | GameProfile gameProfile = security.web$getOwnerProfile(); 60 | GridData gridData = GridData.get(security.web$getSecurityKey()); 61 | grids.add( 62 | new JSON_GridData( 63 | security.web$getSecurityKey(), 64 | grid.web$getCraftingGrid() 65 | .web$getCPUCount(), 66 | gameProfile == null ? "N/A" : gameProfile.getName(), 67 | security.web$hasPermissions(context.getUserID()), 68 | gridData.isTracked)); 69 | } 70 | grids.sort((d1, d2) -> { 71 | if (d1.isOwned && !d2.isOwned) { 72 | return -1; 73 | } else if (!d1.isOwned && d2.isOwned) { 74 | return 1; 75 | } else if (d1.isTrackingEnabled && !d2.isTrackingEnabled) { 76 | return -1; 77 | } else if (!d1.isTrackingEnabled && d2.isTrackingEnabled) { 78 | return 1; 79 | } else if (d1.key == -1 && d2.key != -1) { 80 | return 1; // unattached grids go to the end 81 | } else if (d1.key != -1 && d2.key == -1) { 82 | return -1; // attached grids come first 83 | } else { 84 | return Integer.compare(d2.cpuCount, d1.cpuCount); // sort by cpu count if all else is equal 85 | } 86 | }); 87 | setData(grids); 88 | done(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/GetItems.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Map; 5 | 6 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 7 | import pl.kuba6000.ae2webintegration.core.api.JSON_DetailedItem; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 12 | 13 | public class GetItems extends ISyncedRequest { 14 | 15 | @Override 16 | boolean init(Map getParams) { 17 | return true; 18 | } 19 | 20 | @Override 21 | void handle(IAEGrid grid) { 22 | if (grid == null) { 23 | deny("GRID_NOT_FOUND"); 24 | return; 25 | } 26 | IAEStorageGrid storageGrid = grid.web$getStorageGrid(); 27 | IItemList storageList = storageGrid.web$getItemStorageList(); 28 | AE2Controller.hashcodeToAEItemStack.clear(); 29 | ArrayList items = new ArrayList<>(); 30 | for (IItemStack stack : storageList) { 31 | int hash; 32 | AE2Controller.hashcodeToAEItemStack.put(hash = stack.hashCode(), stack); 33 | JSON_DetailedItem detailedItem = new JSON_DetailedItem(); 34 | detailedItem.itemid = stack.web$getItemID(); 35 | detailedItem.itemname = stack.web$getDisplayName(); 36 | detailedItem.quantity = stack.web$getStackSize(); 37 | detailedItem.craftable = stack.web$isCraftable(); 38 | detailedItem.hashcode = hash; 39 | items.add(detailedItem); 40 | } 41 | setData(items); 42 | done(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/ISyncedRequest.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.Map; 4 | 5 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 6 | import pl.kuba6000.ae2webintegration.core.GridData; 7 | import pl.kuba6000.ae2webintegration.core.ae2request.IRequest; 8 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEControllerState; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IAE; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 13 | 14 | public abstract class ISyncedRequest extends IRequest { 15 | 16 | protected AE2Controller.RequestContext context = null; 17 | protected long gridKey = -1; 18 | protected IAEGrid grid = null; 19 | protected GridData gridData = null; 20 | 21 | boolean init(Map getParams) { 22 | return true; 23 | } 24 | 25 | public boolean init(AE2Controller.RequestContext context) { 26 | this.context = context; 27 | String gridstr = context.getGetParams() 28 | .get("grid"); 29 | if (gridstr == null || gridstr.isEmpty()) gridKey = -1; 30 | else gridKey = Long.parseLong(gridstr); 31 | return init(context.getGetParams()); 32 | } 33 | 34 | void handle(IAEGrid grid) {} 35 | 36 | public void handle(IAE ae) { 37 | if (gridKey != -1) { 38 | for (IAEGrid grid : ae.web$getGrids()) { 39 | IAEPathingGrid pathing = grid.web$getPathingGrid(); 40 | if (pathing == null || pathing.web$isNetworkBooting() 41 | || pathing.web$getControllerState() != AEControllerState.CONTROLLER_ONLINE) { 42 | continue; 43 | } 44 | IAESecurityGrid security = grid.web$getSecurityGrid(); 45 | if (security == null || !security.web$isAvailable()) { 46 | continue; 47 | } 48 | if (gridKey == security.web$getSecurityKey()) { 49 | if (!context.isAdmin() && !security.web$hasPermissions(context.getUserID())) { 50 | deny("NO_PERMISSIONS"); 51 | return; 52 | } 53 | this.grid = grid; 54 | } 55 | } 56 | } 57 | if (grid != null) gridData = GridData.get(gridKey); 58 | handle(grid); 59 | } 60 | 61 | @Override 62 | public void handle(AE2Controller.RequestContext context) { 63 | throw new IllegalArgumentException("ONLY SYNCED"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/Job.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Map; 5 | import java.util.concurrent.ExecutionException; 6 | import java.util.concurrent.Future; 7 | 8 | import net.minecraft.util.IChatComponent; 9 | 10 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 11 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEActionable; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 13 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 14 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEMeInventoryItem; 15 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 16 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 17 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 18 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 19 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 20 | 21 | public class Job extends ISyncedRequest { 22 | 23 | private static class JSON_JobData { 24 | 25 | boolean isDone; 26 | public boolean isSimulating; 27 | public long bytesTotal; 28 | public ArrayList plan; 29 | 30 | public static class JobItem { 31 | 32 | public String itemid; 33 | public String itemname; 34 | public long stored; 35 | public long requested; 36 | public long missing; 37 | public long steps; 38 | public double usedPercent; 39 | } 40 | } 41 | 42 | private enum ERequestType { 43 | CHECK, 44 | CANCEL, 45 | SUBMIT 46 | } 47 | 48 | private ERequestType type = null; 49 | private int jobID; 50 | private String cpuName; 51 | 52 | @Override 53 | boolean init(Map getParams) { 54 | if (!getParams.containsKey("id")) { 55 | noParam("id"); 56 | return false; 57 | } 58 | this.jobID = Integer.parseInt(getParams.get("id")); 59 | if (getParams.containsKey("cancel")) this.type = ERequestType.CANCEL; 60 | else if (getParams.containsKey("submit")) { 61 | this.type = ERequestType.SUBMIT; 62 | if (getParams.containsKey("cpu")) this.cpuName = getParams.get("cpu"); 63 | } else this.type = ERequestType.CHECK; 64 | return true; 65 | } 66 | 67 | @Override 68 | void handle(IAEGrid grid) { 69 | if (grid == null) { 70 | deny("GRID_NOT_FOUND"); 71 | return; 72 | } 73 | Future job = gridData.jobs.get(jobID); 74 | if (job == null) { 75 | deny("INVALID_ID"); 76 | return; 77 | } 78 | if (type == ERequestType.CHECK) { 79 | JSON_JobData jobData = new JSON_JobData(); 80 | if (jobData.isDone = job.isDone()) { 81 | try { 82 | IAECraftingJob craftingJob = job.get(); 83 | IAEStorageGrid storageGrid = grid.web$getStorageGrid(); 84 | IAEMeInventoryItem items = storageGrid.web$getItemInventory(); 85 | jobData.isSimulating = craftingJob.web$isSimulation(); 86 | jobData.bytesTotal = craftingJob.web$getByteTotal(); 87 | IItemList plan; 88 | craftingJob.web$populatePlan(plan = AE2Controller.AE2Interface.web$createItemList()); 89 | jobData.plan = new ArrayList<>(); 90 | for (IItemStack stack : plan) { 91 | JSON_JobData.JobItem jobItem = new JSON_JobData.JobItem(); 92 | jobItem.itemid = stack.web$getItemID(); 93 | jobItem.itemname = stack.web$getDisplayName(); 94 | jobItem.requested = stack.web$getCountRequestable(); 95 | jobItem.steps = stack.web$getCountRequestableCrafts(); 96 | if (jobData.isSimulating) { 97 | IItemStack toExtract = stack.web$copy(); 98 | toExtract.web$reset(); 99 | toExtract.web$setStackSize(stack.web$getStackSize()); 100 | IItemStack missing = toExtract.web$copy(); 101 | toExtract = items.web$extractItems(toExtract, AEActionable.SIMULATE, grid); 102 | if (toExtract == null) { 103 | toExtract = missing.web$copy(); 104 | toExtract.web$setStackSize(0); 105 | } 106 | jobItem.stored = toExtract.web$getStackSize(); 107 | jobItem.missing = missing.web$getStackSize() - toExtract.web$getStackSize(); 108 | } else { 109 | jobItem.stored = stack.web$getStackSize(); 110 | jobItem.missing = 0; 111 | } 112 | if (jobItem.missing == 0 && jobItem.requested == 0 && jobItem.stored > 0) { 113 | IItemStack realStack = items.web$getAvailableItem(stack); 114 | long available = 0L; 115 | if (realStack != null) available = realStack.web$getStackSize(); 116 | if (available > 0L) jobItem.usedPercent = (double) jobItem.stored / (double) available; 117 | } 118 | jobData.plan.add(jobItem); 119 | } 120 | // TODO Move sorting to javascript! 121 | jobData.plan.sort((i1, i2) -> { 122 | if (i1.missing > 0 && i2.missing > 0) return Long.compare(i2.missing, i1.missing); 123 | else if (i1.missing > 0 && i2.missing == 0) return -1; 124 | else if (i1.missing == 0 && i2.missing > 0) return 1; 125 | if (i1.requested > 0 && i2.requested > 0) return Long.compare(i2.steps, i1.steps); 126 | else if (i1.requested > 0 && i2.requested == 0) return -1; 127 | else if (i1.requested == 0 && i2.requested > 0) return 1; 128 | return Long.compare(i2.stored, i1.stored); 129 | }); 130 | } catch (InterruptedException | ExecutionException e) { 131 | e.printStackTrace(); 132 | deny("INTERNAL_ERROR"); 133 | return; 134 | } 135 | } 136 | setData(jobData); 137 | done(); 138 | } else if (type == ERequestType.CANCEL) { 139 | job.cancel(true); 140 | gridData.jobs.remove(this.jobID); 141 | done(); 142 | } else if (type == ERequestType.SUBMIT) { 143 | IAECraftingGrid craftingGrid = grid.web$getCraftingGrid(); 144 | if (job.isDone()) { 145 | try { 146 | IAECraftingJob craftingJob = job.get(); 147 | ICraftingCPUCluster target = null; 148 | if (cpuName != null) { 149 | target = GetCPUList.getCPUList(craftingGrid) 150 | .get(cpuName); 151 | if (target == null) { 152 | deny("CPU_NOT_FOUND"); 153 | return; 154 | } 155 | } 156 | IChatComponent error = craftingGrid.web$submitJob(craftingJob, target, true, grid); 157 | if (error != null) { 158 | deny("FAIL"); 159 | setData(error.getUnformattedTextForChat()); 160 | } else { 161 | done(); 162 | } 163 | } catch (InterruptedException | ExecutionException e) { 164 | e.printStackTrace(); 165 | deny("INTERNAL_ERROR"); 166 | } 167 | } else { 168 | deny("JOB_NOT_DONE"); 169 | } 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/ae2request/sync/Order.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.ae2request.sync; 2 | 3 | import static pl.kuba6000.ae2webintegration.core.AE2Controller.hashcodeToAEItemStack; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.Future; 7 | 8 | import com.google.gson.JsonObject; 9 | 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 12 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 13 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 14 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 15 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 16 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 17 | 18 | public class Order extends ISyncedRequest { 19 | 20 | private IItemStack item; 21 | 22 | @Override 23 | boolean init(Map getParams) { 24 | if (!getParams.containsKey("item") || !getParams.containsKey("quantity")) { 25 | noParam("item", "quantity"); 26 | return false; 27 | } 28 | int hash = Integer.parseInt(getParams.get("item")); 29 | int quantity = Integer.parseInt(getParams.get("quantity")); 30 | this.item = hashcodeToAEItemStack.get(hash); 31 | if (this.item == null || !this.item.web$isCraftable()) { 32 | deny("ITEM_NOT_FOUND"); 33 | return false; 34 | } 35 | this.item = this.item.web$copy(); 36 | this.item.web$setStackSize(quantity); 37 | return true; 38 | } 39 | 40 | @Override 41 | void handle(IAEGrid grid) { 42 | if (grid == null) { 43 | deny("GRID_NOT_FOUND"); 44 | return; 45 | } 46 | IAECraftingGrid craftingGrid = grid.web$getCraftingGrid(); 47 | boolean allBusy = true; 48 | for (ICraftingCPUCluster cpu : craftingGrid.web$getCPUs()) { 49 | if (!cpu.web$isBusy()) { 50 | allBusy = false; 51 | break; 52 | } 53 | } 54 | if (!allBusy) { 55 | IAEStorageGrid storageGrid = grid.web$getStorageGrid(); 56 | final IItemList itemList = storageGrid.web$getItemStorageList(); 57 | IItemStack realItem = itemList.web$findPrecise(this.item); 58 | if (realItem != null && realItem.web$isCraftable()) { 59 | Future job = craftingGrid.web$beginCraftingJob(grid, this.item); 60 | 61 | int jobID = gridData.addJob(job); 62 | JsonObject jobData = new JsonObject(); 63 | jobData.addProperty("jobID", jobID); 64 | if (gridData.jobs.size() > 3) { 65 | int toDeleteBelowAndEqual = jobID - 3; 66 | gridData.jobs.entrySet() 67 | .removeIf(integerFutureEntry -> integerFutureEntry.getKey() <= toDeleteBelowAndEqual); 68 | } 69 | setData(jobData); 70 | done(); 71 | } else { 72 | deny("ITEM_NOT_FOUND"); 73 | } 74 | } else { 75 | deny("ALL_CPU_BUSY"); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/AEApi/AEActionable.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api.AEApi; 2 | 3 | public enum AEActionable { 4 | MODULATE, 5 | SIMULATE 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/AEApi/AEControllerState.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api.AEApi; 2 | 3 | public enum AEControllerState { 4 | NO_CONTROLLER, 5 | CONTROLLER_ONLINE, 6 | CONTROLLER_CONFLICT, 7 | // not implemented 8 | UNSUPPORTED 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/DimensionalCoords.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | import java.util.Objects; 4 | 5 | import net.minecraft.world.World; 6 | 7 | public class DimensionalCoords { 8 | 9 | int dimid; 10 | int x; 11 | int y; 12 | int z; 13 | 14 | public DimensionalCoords(int dimid, int x, int y, int z) { 15 | this.dimid = dimid; 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public DimensionalCoords(World world, int x, int y, int z) { 22 | this.dimid = world.provider.dimensionId; 23 | this.x = x; 24 | this.y = y; 25 | this.z = z; 26 | } 27 | 28 | @Override 29 | public int hashCode() { 30 | return Objects.hash(dimid, x, y, z); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object obj) { 35 | return obj instanceof DimensionalCoords coords && coords.dimid == dimid 36 | && coords.x == x 37 | && coords.y == y 38 | && coords.z == z; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/IAEMixinCallbacks.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | import pl.kuba6000.ae2webintegration.core.AEMixinCallbacks; 4 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingPatternDetails; 5 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IPatternProviderViewable; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 10 | 11 | public interface IAEMixinCallbacks { 12 | 13 | static IAEMixinCallbacks getInstance() { 14 | return AEMixinCallbacks.INSTANCE; 15 | } 16 | 17 | void jobStarted(ICraftingCPUCluster cpuCluster, IAECraftingGrid cache, IAEGrid grid, boolean isMerging, 18 | boolean isAuthorPlayer); 19 | 20 | void craftingStatusPostedUpdate(ICraftingCPUCluster cpu, IItemStack diff); 21 | 22 | void pushedPattern(ICraftingCPUCluster cpu, IPatternProviderViewable provider, IAECraftingPatternDetails details); 23 | 24 | void jobCompleted(IAEGrid grid, ICraftingCPUCluster cpu); 25 | 26 | void jobCancelled(IAEGrid grid, ICraftingCPUCluster cpu); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/IAEWebInterface.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | 5 | import pl.kuba6000.ae2webintegration.core.AEWebAPI; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.IAE; 7 | 8 | public interface IAEWebInterface { 9 | 10 | static IAEWebInterface getInstance() { 11 | return AEWebAPI.INSTANCE; 12 | } 13 | 14 | GameProfile getAEWebGameProfile(); 15 | 16 | void initAEInterface(IAE ae); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/JSON_CompactedItem.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 4 | import pl.kuba6000.ae2webintegration.core.utils.GSONUtils; 5 | 6 | public class JSON_CompactedItem { 7 | 8 | @GSONUtils.SkipGSON 9 | private final IItemStack internalItem; 10 | @GSONUtils.SkipGSON 11 | private final int hashcode; 12 | 13 | public final String itemid; 14 | public final String itemname; 15 | public long active = 0; 16 | public long pending = 0; 17 | public long stored = 0; 18 | public long timeSpentCrafting = 0; 19 | public long craftedTotal = 0; 20 | public double shareInCraftingTime = 0d; 21 | public double shareInCraftingTimeCombined = 0d; 22 | public double craftsPerSec = 0d; 23 | 24 | public JSON_CompactedItem(IItemStack itemStack) { 25 | this.internalItem = itemStack; 26 | this.hashcode = this.internalItem.hashCode(); 27 | this.itemid = itemStack.web$getItemID(); 28 | this.itemname = itemStack.web$getDisplayName(); 29 | } 30 | 31 | public static JSON_CompactedItem create(IItemStack stack) { 32 | return new JSON_CompactedItem(stack); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return hashcode; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object obj) { 42 | if (obj instanceof JSON_CompactedItem) { 43 | return ((JSON_CompactedItem) obj).internalItem.equals(this.internalItem); 44 | } 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/JSON_CompactedJobTrackingInfo.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.lang3.tuple.Pair; 8 | 9 | import pl.kuba6000.ae2webintegration.core.AE2JobTracker; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 11 | 12 | public class JSON_CompactedJobTrackingInfo { 13 | 14 | public static class timingClass { 15 | 16 | long started; 17 | long ended; 18 | 19 | public timingClass(long started, long ended) { 20 | this.started = started; 21 | this.ended = ended; 22 | } 23 | } 24 | 25 | public static class CompactedTrackingGSONItem { 26 | 27 | public String itemid; 28 | public String itemname; 29 | public long timeSpentOn; 30 | public long craftedTotal; 31 | public double shareInCraftingTime = 0d; 32 | public double shareInCraftingTimeCombined = 0d; 33 | public double craftsPerSec = 0d; 34 | 35 | public ArrayList timings = new ArrayList<>(); 36 | } 37 | 38 | public IItemStack finalOutput; 39 | public long timeStarted; 40 | public long timeDone; 41 | public boolean wasCancelled; 42 | public ArrayList items = new ArrayList<>(); 43 | 44 | public static class AEInterfaceGSON { 45 | 46 | String name; 47 | 48 | public ArrayList timings = new ArrayList<>(); 49 | public long timingsCombined; 50 | 51 | public HashSet location = new HashSet<>(); 52 | } 53 | 54 | public ArrayList interfaceShare = new ArrayList<>(); 55 | 56 | public JSON_CompactedJobTrackingInfo(AE2JobTracker.JobTrackingInfo info) { 57 | this.finalOutput = info.finalOutput; 58 | this.timeStarted = info.timeStarted; 59 | this.timeDone = info.timeDone; 60 | long elapsed = this.timeDone - this.timeStarted; 61 | this.wasCancelled = info.wasCancelled; 62 | for (Map.Entry entry : info.timeSpentOn.entrySet()) { 63 | IItemStack stack = entry.getKey(); 64 | long spent = entry.getValue(); 65 | CompactedTrackingGSONItem item = new CompactedTrackingGSONItem(); 66 | item.itemid = stack.web$getItemID(); 67 | item.itemname = stack.web$getDisplayName(); 68 | item.timeSpentOn = spent; 69 | item.craftedTotal = info.craftedTotal.get(stack); 70 | item.shareInCraftingTime = info.getShareInCraftingTime(stack); 71 | item.shareInCraftingTimeCombined = Math.min(((double) item.timeSpentOn) / (double) elapsed, 1d); 72 | item.craftsPerSec = (double) item.craftedTotal / (item.timeSpentOn / 1000d); 73 | for (Pair longLongPair : info.itemShare.get(stack)) { 74 | item.timings.add(new timingClass(longLongPair.getKey(), longLongPair.getValue())); 75 | } 76 | items.add(item); 77 | } 78 | items.sort((i1, i2) -> Double.compare(i2.shareInCraftingTime, i1.shareInCraftingTime)); 79 | for (Map.Entry>> entry : info.interfaceShare.entrySet()) { 80 | AEInterfaceGSON interfaceGSON = new AEInterfaceGSON(); 81 | interfaceGSON.name = entry.getKey().name; 82 | interfaceGSON.location = entry.getKey().location; 83 | for (Pair longLongPair : entry.getValue()) { 84 | interfaceGSON.timings.add(new timingClass(longLongPair.getKey(), longLongPair.getValue())); 85 | } 86 | long interfaceElapsed = 0L; 87 | for (Pair pair : entry.getValue()) { 88 | interfaceElapsed += pair.getValue() - pair.getKey(); 89 | } 90 | interfaceGSON.timingsCombined = interfaceElapsed; 91 | interfaceShare.add(interfaceGSON); 92 | } 93 | interfaceShare.sort((i1, i2) -> Long.compare(i2.timingsCombined, i1.timingsCombined)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/api/JSON_DetailedItem.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.api; 2 | 3 | public class JSON_DetailedItem { 4 | 5 | public int hashcode; 6 | public String itemid; 7 | public String itemname; 8 | public long quantity; 9 | public boolean craftable; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/commands/BaseCommandHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.commands; 2 | 3 | import java.util.UUID; 4 | 5 | import net.minecraft.command.CommandBase; 6 | import net.minecraft.command.ICommandSender; 7 | import net.minecraft.entity.player.EntityPlayerMP; 8 | import net.minecraft.util.ChatComponentText; 9 | import net.minecraft.util.ChatComponentTranslation; 10 | import net.minecraft.util.EnumChatFormatting; 11 | 12 | import org.apache.commons.lang3.tuple.Pair; 13 | 14 | import pl.kuba6000.ae2webintegration.core.AE2Controller; 15 | import pl.kuba6000.ae2webintegration.core.Config; 16 | import pl.kuba6000.ae2webintegration.core.WebData; 17 | 18 | public class BaseCommandHandler extends CommandBase { 19 | 20 | @Override 21 | public String getCommandName() { 22 | return "ae2webintegration"; 23 | } 24 | 25 | @Override 26 | public String getCommandUsage(ICommandSender sender) { 27 | return "ae2webintegration "; 28 | } 29 | 30 | @Override 31 | public int getRequiredPermissionLevel() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public void processCommand(ICommandSender sender, String[] args) { 37 | if (sender.getEntityWorld().isRemote) return; 38 | if (args.length == 0 || (!args[0].equals("reload") && !args[0].equals("auth"))) { 39 | sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "/ae2webintegration ")); 40 | return; 41 | } 42 | if (args[0].equals("reload")) { 43 | if (!sender.canCommandSenderUseCommand(4, getCommandName())) { 44 | ChatComponentTranslation chatcomponenttranslation2 = new ChatComponentTranslation( 45 | "commands.generic.permission", 46 | new Object[0]); 47 | chatcomponenttranslation2.getChatStyle() 48 | .setColor(EnumChatFormatting.RED); 49 | sender.addChatMessage(chatcomponenttranslation2); 50 | return; 51 | } 52 | Config.synchronizeConfiguration(); 53 | AE2Controller.stopHTTPServer(); 54 | AE2Controller.startHTTPServer(); 55 | sender.addChatMessage( 56 | new ChatComponentText( 57 | EnumChatFormatting.GREEN + "Successfully reloaded the config and restarted the web server!")); 58 | } else { 59 | // auth command 60 | if (args.length < 2) { 61 | sender 62 | .addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "/ae2webintegration auth ")); 63 | return; 64 | } 65 | 66 | String token = args[1]; 67 | 68 | if (!(sender instanceof EntityPlayerMP)) { 69 | sender.addChatMessage( 70 | new ChatComponentText(EnumChatFormatting.RED + "This command can only be used by players!")); 71 | return; 72 | } 73 | 74 | UUID id = ((EntityPlayerMP) sender).getUniqueID(); 75 | 76 | Pair p = AE2Controller.awaitingRegistration.get(id); 77 | if (p == null) { 78 | sender.addChatMessage( 79 | new ChatComponentText( 80 | EnumChatFormatting.RED 81 | + "You have to initialize the registration on the web interface first!")); 82 | return; 83 | } 84 | 85 | if (!p.getLeft() 86 | .equals(token)) { 87 | sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Invalid token!")); 88 | return; 89 | } 90 | 91 | WebData.setPassword(((EntityPlayerMP) sender).getGameProfile(), p.getRight()); 92 | 93 | AE2Controller.awaitingRegistration.remove(id); 94 | 95 | sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN + "Registered successfully!")); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/discord/DiscordManager.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.discord; 2 | 3 | import static pl.kuba6000.ae2webintegration.core.AE2WebIntegration.MODID; 4 | 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.net.URL; 8 | import java.util.concurrent.ConcurrentLinkedQueue; 9 | 10 | import javax.net.ssl.HttpsURLConnection; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.JsonObject; 17 | 18 | import pl.kuba6000.ae2webintegration.core.Config; 19 | 20 | public class DiscordManager extends Thread { 21 | 22 | private static final Logger LOG = LogManager.getLogger(MODID + " - DISCORD INTEGRATION"); 23 | 24 | private static DiscordManager thread; 25 | 26 | private static ConcurrentLinkedQueue toPush = new ConcurrentLinkedQueue<>(); 27 | 28 | public static void init() { 29 | if (thread != null) return; 30 | thread = new DiscordManager(); 31 | thread.start(); 32 | } 33 | 34 | public static void postMessageNonBlocking(DiscordEmbed message) { 35 | toPush.offer(message); 36 | } 37 | 38 | public static class DiscordEmbed { 39 | 40 | String title; 41 | String description; 42 | int color; 43 | 44 | public DiscordEmbed(String title, String description, int color) { 45 | this.title = title; 46 | this.description = description; 47 | this.color = color; 48 | } 49 | 50 | public DiscordEmbed(String title, String description) { 51 | this(title, description, 1752220); 52 | } 53 | } 54 | 55 | private static void postMessage(DiscordEmbed message) { 56 | if (Config.DISCORD_WEBHOOK.isEmpty()) return; 57 | 58 | String roleID = Config.DISCORD_ROLE_ID; 59 | 60 | JsonObject json = new JsonObject(); 61 | json.addProperty("username", "AE2 Web Integration"); 62 | json.addProperty("content", !roleID.isEmpty() ? "<@&" + roleID + ">" : ""); 63 | JsonArray embeds = new JsonArray(); 64 | JsonObject embed = new JsonObject(); 65 | embed.addProperty("title", message.title); 66 | embed.addProperty("description", message.description); 67 | embed.addProperty("color", message.color); 68 | embeds.add(embed); 69 | json.add("embeds", embeds); 70 | json.add("attachments", new JsonArray()); 71 | 72 | URL url = null; 73 | try { 74 | url = new URL(Config.DISCORD_WEBHOOK); 75 | 76 | HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 77 | connection.addRequestProperty("Content-Type", "application/json"); 78 | connection.addRequestProperty("User-Agent", "AE2-Web-Integration"); 79 | connection.setDoOutput(true); 80 | connection.setRequestMethod("POST"); 81 | 82 | OutputStream stream = connection.getOutputStream(); 83 | stream.write( 84 | json.toString() 85 | .getBytes()); 86 | stream.flush(); 87 | stream.close(); 88 | 89 | int code; 90 | if ((code = connection.getResponseCode()) != 200 && code != 204) { 91 | LOG.error("Error, response code: {}", code); 92 | } 93 | } catch (IOException e) { 94 | // throw new RuntimeException(e); 95 | } 96 | } 97 | 98 | @Override 99 | public void run() { 100 | while (true) { 101 | if (toPush.peek() != null) { 102 | DiscordEmbed message; 103 | while ((message = toPush.poll()) != null) { 104 | postMessage(message); 105 | } 106 | } 107 | 108 | try { 109 | Thread.sleep(1000); 110 | } catch (InterruptedException e) { 111 | // throw new RuntimeException(e); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAE.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface IAE { 4 | 5 | Iterable web$getGrids(); 6 | 7 | IItemList web$createItemList(); 8 | 9 | IAEPlayerData web$getPlayerData(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAECraftingJob.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface IAECraftingJob { 4 | 5 | boolean web$isSimulation(); 6 | 7 | long web$getByteTotal(); 8 | 9 | void web$populatePlan(IItemList plan); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAECraftingPatternDetails.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface IAECraftingPatternDetails { 4 | 5 | IItemStack[] web$getCondensedOutputs(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAEGrid.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | import net.minecraft.util.IChatComponent; 4 | 5 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAECraftingGrid; 6 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEPathingGrid; 7 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAESecurityGrid; 8 | import pl.kuba6000.ae2webintegration.core.interfaces.service.IAEStorageGrid; 9 | 10 | public interface IAEGrid { 11 | 12 | IAECraftingGrid web$getCraftingGrid(); 13 | 14 | IAEPathingGrid web$getPathingGrid(); 15 | 16 | IAEStorageGrid web$getStorageGrid(); 17 | 18 | IAESecurityGrid web$getSecurityGrid(); 19 | 20 | boolean web$isEmpty(); 21 | 22 | Object web$getPlayerSource(); 23 | 24 | IChatComponent web$getLastFakePlayerChatMessage(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAEMeInventoryItem.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEActionable; 4 | 5 | public interface IAEMeInventoryItem { 6 | 7 | IItemStack web$extractItems(IItemStack stack, AEActionable mode, IAEGrid grid); 8 | 9 | IItemStack web$getAvailableItem(IItemStack stack); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IAEPlayerData.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | 5 | public interface IAEPlayerData { 6 | 7 | GameProfile web$getPlayerProfile(int playerId); 8 | 9 | int web$getPlayerId(GameProfile id); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/ICraftingCPUCluster.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface ICraftingCPUCluster { 4 | 5 | void web$setInternalID(int id); 6 | 7 | boolean web$hasCustomName(); 8 | 9 | String web$getName(); 10 | 11 | long web$getAvailableStorage(); 12 | 13 | long web$getUsedStorage(); 14 | 15 | long web$getCoProcessors(); 16 | 17 | boolean web$isBusy(); 18 | 19 | void web$cancel(); 20 | 21 | IItemStack web$getFinalOutput(); 22 | 23 | void web$getActiveItems(IItemList list); 24 | 25 | void web$getPendingItems(IItemList list); 26 | 27 | void web$getStorageItems(IItemList list); 28 | 29 | IItemList web$getWaitingFor(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IItemList.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface IItemList extends Iterable { 4 | 5 | IItemStack web$findPrecise(IItemStack stack); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IItemStack.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | public interface IItemStack { 4 | 5 | String web$getItemID(); 6 | 7 | String web$getDisplayName(); 8 | 9 | long web$getStackSize(); 10 | 11 | boolean web$isCraftable(); 12 | 13 | long web$getCountRequestable(); 14 | 15 | long web$getCountRequestableCrafts(); 16 | 17 | void web$reset(); 18 | 19 | boolean web$isSameType(IItemStack other); 20 | 21 | IItemStack web$copy(); 22 | 23 | void web$setStackSize(long size); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/IPatternProviderViewable.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces; 2 | 3 | import pl.kuba6000.ae2webintegration.core.api.DimensionalCoords; 4 | 5 | public interface IPatternProviderViewable { 6 | 7 | String web$getName(); 8 | 9 | DimensionalCoords web$getLocation(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/service/IAECraftingGrid.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces.service; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.Future; 5 | 6 | import net.minecraft.util.IChatComponent; 7 | 8 | import pl.kuba6000.ae2webintegration.core.interfaces.IAECraftingJob; 9 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEGrid; 10 | import pl.kuba6000.ae2webintegration.core.interfaces.ICraftingCPUCluster; 11 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 12 | 13 | public interface IAECraftingGrid { 14 | 15 | int web$getCPUCount(); 16 | 17 | Set web$getCPUs(); 18 | 19 | Future web$beginCraftingJob(IAEGrid grid, IItemStack stack); 20 | 21 | IChatComponent web$submitJob(IAECraftingJob job, ICraftingCPUCluster target, boolean prioritizePower, IAEGrid grid); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/service/IAEPathingGrid.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces.service; 2 | 3 | import pl.kuba6000.ae2webintegration.core.api.AEApi.AEControllerState; 4 | 5 | public interface IAEPathingGrid { 6 | 7 | boolean web$isNetworkBooting(); 8 | 9 | AEControllerState web$getControllerState(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/service/IAESecurityGrid.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces.service; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | 5 | public interface IAESecurityGrid { 6 | 7 | boolean web$isAvailable(); 8 | 9 | long web$getSecurityKey(); 10 | 11 | int web$getOwner(); 12 | 13 | GameProfile web$getOwnerProfile(); 14 | 15 | boolean web$hasPermissions(int playerId); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/interfaces/service/IAEStorageGrid.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.interfaces.service; 2 | 3 | import pl.kuba6000.ae2webintegration.core.interfaces.IAEMeInventoryItem; 4 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemList; 5 | 6 | public interface IAEStorageGrid { 7 | 8 | IItemList web$getItemStorageList(); 9 | 10 | IAEMeInventoryItem web$getItemInventory(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/utils/GSONUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.utils; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import com.google.gson.ExclusionStrategy; 9 | import com.google.gson.FieldAttributes; 10 | import com.google.gson.GsonBuilder; 11 | import com.google.gson.JsonObject; 12 | import com.google.gson.JsonSerializer; 13 | 14 | import pl.kuba6000.ae2webintegration.core.interfaces.IItemStack; 15 | 16 | public class GSONUtils { 17 | 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Target(ElementType.FIELD) 20 | public @interface SkipGSON {} 21 | 22 | private static final ExclusionStrategy GSONStrategy = new ExclusionStrategy() { 23 | 24 | @Override 25 | public boolean shouldSkipField(FieldAttributes f) { 26 | return f.getAnnotation(SkipGSON.class) != null; 27 | } 28 | 29 | @Override 30 | public boolean shouldSkipClass(Class clazz) { 31 | return false; 32 | } 33 | }; 34 | 35 | private static final JsonSerializer IItemStackSerializer = (src, typeOfSrc, context) -> { 36 | JsonObject json = new JsonObject(); 37 | json.addProperty("itemid", src.web$getItemID()); 38 | json.addProperty("itemname", src.web$getDisplayName()); 39 | json.addProperty("hashcode", src.hashCode()); 40 | json.addProperty("quantity", src.web$getStackSize()); 41 | return json; 42 | }; 43 | 44 | public static final GsonBuilder GSON_BUILDER = new GsonBuilder().addSerializationExclusionStrategy(GSONStrategy) 45 | .addDeserializationExclusionStrategy(GSONStrategy) 46 | .registerTypeHierarchyAdapter(IItemStack.class, IItemStackSerializer) 47 | .serializeNulls(); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/utils/HTTPUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLDecoder; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class HTTPUtils { 9 | 10 | public static Map parseQueryString(String qs) { 11 | Map result = new HashMap<>(); 12 | if (qs == null) return result; 13 | 14 | int last = 0, next, l = qs.length(); 15 | while (last < l) { 16 | next = qs.indexOf('&', last); 17 | if (next == -1) next = l; 18 | 19 | if (next > last) { 20 | int eqPos = qs.indexOf('=', last); 21 | try { 22 | if (eqPos < 0 || eqPos > next) result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); 23 | else result.put( 24 | URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), 25 | URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); 26 | } catch (UnsupportedEncodingException e) { 27 | throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java 28 | } 29 | } 30 | last = next + 1; 31 | } 32 | return result; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/utils/RateLimiter.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.utils; 2 | 3 | import java.net.InetAddress; 4 | import java.util.HashMap; 5 | 6 | public class RateLimiter { 7 | 8 | private final int MAX_REQUESTS_PER_INTERVAL; 9 | private final int RESET_INTERVAL_MS; 10 | private final int RESET_WHITELIST_INTERVAL_MS; // 1 hour 11 | 12 | public RateLimiter(int maxRequestsPerInterval, int resetIntervalMs, int resetWhitelistIntervalMs) { 13 | MAX_REQUESTS_PER_INTERVAL = maxRequestsPerInterval; 14 | RESET_INTERVAL_MS = resetIntervalMs; 15 | RESET_WHITELIST_INTERVAL_MS = resetWhitelistIntervalMs; 16 | } 17 | 18 | private long lastUpdate = 0; 19 | private final HashMap requestCounter = new HashMap<>(); 20 | private final HashMap whitelist = new HashMap<>(); 21 | 22 | public boolean isAllowed(InetAddress userId) { 23 | updateRequests(); 24 | 25 | if (whitelist.containsKey(userId)) { 26 | return true; // User is whitelisted 27 | } 28 | 29 | return requestCounter.merge(userId, 1, Integer::sum) < MAX_REQUESTS_PER_INTERVAL; 30 | } 31 | 32 | public void ensureWhitelisted(InetAddress userId) { 33 | whitelist.put(userId, System.currentTimeMillis()); 34 | } 35 | 36 | private void updateRequests() { 37 | long currentTime = System.currentTimeMillis(); 38 | 39 | if (currentTime - lastUpdate > RESET_INTERVAL_MS) { // Reset every 60 seconds 40 | requestCounter.clear(); 41 | lastUpdate = currentTime; 42 | } 43 | 44 | whitelist.entrySet() 45 | .removeIf(entry -> currentTime - entry.getValue() > RESET_WHITELIST_INTERVAL_MS); // Remove entries older 46 | // than 1 hour 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/pl/kuba6000/ae2webintegration/core/utils/VersionChecker.java: -------------------------------------------------------------------------------- 1 | package pl.kuba6000.ae2webintegration.core.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStreamReader; 5 | import java.net.HttpURLConnection; 6 | import java.net.URL; 7 | 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonParser; 10 | 11 | import pl.kuba6000.ae2webintegration.Tags; 12 | 13 | public class VersionChecker { 14 | 15 | // example version: 0.0.9-alpha-forge-1.12.2 16 | private static final String VERSION_IDENTIFIER = "-forge-1.7.10"; 17 | 18 | private static final String versionCheckURL = "https://api.github.com/repos/kuba6000/AE2-Web-Integration/tags"; 19 | private static String latestTag = null; 20 | 21 | private static long lastChecked = 0L; 22 | 23 | private static void updateLatestVersion() { 24 | if (lastChecked != 0L) { 25 | if (!Tags.VERSION.equals(latestTag)) return; 26 | long elapsed = System.currentTimeMillis() - lastChecked; 27 | if (latestTag == null) { 28 | if (elapsed < 5 * 60 * 1000) // 5 minutes 29 | return; 30 | } else if (elapsed < 5 * 60 * 60 * 1000) { // 5 hours 31 | return; 32 | } 33 | } 34 | lastChecked = System.currentTimeMillis(); 35 | try { 36 | HttpURLConnection conn = (HttpURLConnection) new URL(versionCheckURL).openConnection(); 37 | if (conn.getResponseCode() == 200) { 38 | try (BufferedReader buf = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { 39 | JsonElement element = new JsonParser().parse(buf); 40 | // this should be sorted right? 41 | for (JsonElement tag : element.getAsJsonArray()) { 42 | String name = tag.getAsJsonObject() 43 | .get("name") 44 | .getAsString(); 45 | if (name.contains(VERSION_IDENTIFIER)) { 46 | latestTag = name; 47 | return; 48 | } 49 | } 50 | // not found??? 51 | latestTag = Tags.VERSION; 52 | } 53 | } 54 | 55 | } catch (Exception ignored) { 56 | 57 | } 58 | } 59 | 60 | public static boolean isOutdated() { 61 | updateLatestVersion(); 62 | if (latestTag == null) return false; 63 | return !latestTag.equals(Tags.VERSION); 64 | } 65 | 66 | public static String getLatestTag() { 67 | return latestTag; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/resources/LICENSE: -------------------------------------------------------------------------------- 1 | AE2 Web Integration - Minecraft addon 2 | Copyright (C) 2024 kuba6000 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 3 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public License 15 | along with this library. If not, see . 16 | -------------------------------------------------------------------------------- /src/main/resources/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuba6000/AE2-Web-Integration/9aa6e6fe27e58b443b077db3007b48e34e78f9a9/src/main/resources/assets/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/assets/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 150 | 151 | 152 | 153 | AE2 154 | 155 | 156 |

UNIVERSAL WEB TERMINAL

157 | 170 |
171 |
172 |
173 | 174 |
175 |
176 |
177 | This service requires authentication

178 |
179 |
180 |
181 |
182 |


183 | 184 |
185 |
186 | Or register

187 |
188 |
189 |
190 |
191 |

192 | 193 |
194 |
195 |
196 |
197 | 198 | 251 | 252 |

253 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | { 2 | "modListVersion": 2, 3 | "modList": [{ 4 | "modid": "ae2webintegration-core", 5 | "name": "AE2WebIntegration-Core", 6 | "description": "AE2 Web Integration mod", 7 | "version": "${modVersion}", 8 | "mcversion": "*", 9 | "url": "https://github.com/kuba6000/AE2-Web-Integration", 10 | "updateUrl": "", 11 | "authorList": ["kuba6000"], 12 | "credits": "", 13 | "logoFile": "", 14 | "screenshots": [], 15 | "parent": "", 16 | "requiredMods": [], 17 | "dependencies": [], 18 | "dependants": [], 19 | "useDependencyInformation": false 20 | }, 21 | { 22 | "modid": "ae2webintegration-interface", 23 | "name": "AE2WebIntegration-Interface", 24 | "description": "AE2 Web Integration mod", 25 | "version": "${modVersion}", 26 | "mcversion": "${minecraftVersion}", 27 | "url": "https://github.com/kuba6000/AE2-Web-Integration", 28 | "updateUrl": "", 29 | "authorList": ["kuba6000"], 30 | "credits": "", 31 | "logoFile": "", 32 | "screenshots": [], 33 | "parent": "", 34 | "requiredMods": [], 35 | "dependencies": [], 36 | "dependants": [], 37 | "useDependencyInformation": false 38 | }] 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/mixins.ae2webintegration.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.5-GTNH", 4 | "package": "pl.kuba6000.ae2webintegration.ae2interface.mixins", 5 | "plugin": "pl.kuba6000.ae2webintegration.ae2interface.mixins.MixinPlugin", 6 | "refmap": "mixins.ae2webintegration.refmap.json", 7 | "target": "@env(DEFAULT)", 8 | "compatibilityLevel": "JAVA_8", 9 | "mixins": [], 10 | "client": [], 11 | "server": [] 12 | } 13 | --------------------------------------------------------------------------------