├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
└── workflows
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── README_zh.md
├── TEMPLATE_LICENSE.txt
├── build.gradle
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── crafting.png
├── storage.png
└── ui.png
├── settings.gradle
└── src
├── frontend
└── frontend
│ ├── assets
│ ├── index-C1o9Z9di.js
│ └── index-EpFq6eZJ.css
│ ├── favicon.png
│ ├── index.html
│ └── lang
│ ├── all.json
│ ├── en_ud.json
│ ├── en_us.json
│ └── zh_cn.json
├── generated
└── resources
│ ├── assets
│ └── appwebterminal
│ │ ├── blockstates
│ │ └── web_terminal.json
│ │ ├── lang
│ │ ├── en_ud.json
│ │ └── en_us.json
│ │ └── models
│ │ └── item
│ │ └── web_terminal.json
│ └── data
│ ├── appwebterminal
│ ├── advancements
│ │ └── recipes
│ │ │ └── misc
│ │ │ ├── cable_web_terminal_from_block.json
│ │ │ ├── cable_web_terminal_from_part.json
│ │ │ └── web_terminal.json
│ ├── loot_tables
│ │ └── blocks
│ │ │ └── web_terminal.json
│ └── recipes
│ │ ├── cable_web_terminal_from_block.json
│ │ ├── cable_web_terminal_from_part.json
│ │ └── web_terminal.json
│ └── minecraft
│ └── tags
│ └── blocks
│ ├── mineable
│ └── pickaxe.json
│ └── needs_stone_tool.json
└── main
├── java
└── icu
│ └── takeneko
│ └── appwebterminal
│ ├── api
│ └── ImageProvider.java
│ ├── config
│ ├── AppWebTerminalConfig.java
│ └── MinecraftAssetsApi.java
│ └── mixins
│ ├── LevelRendererMixin.java
│ └── RenderStateShardMixin.java
├── kotlin
└── icu
│ └── takeneko
│ └── appwebterminal
│ ├── AppWebTerminal.kt
│ ├── all
│ ├── BlockEntities.kt
│ ├── Blocks.kt
│ ├── Commands.kt
│ ├── CreativeTabs.kt
│ ├── Events.kt
│ ├── Items.kt
│ ├── KeyImageProviders.kt
│ ├── Networking.kt
│ └── Registries.kt
│ ├── api
│ ├── KeyImageProviderLoader.kt
│ └── crafting
│ │ ├── CraftingCpuContainer.kt
│ │ ├── CraftingCpuContainerHelper.kt
│ │ └── impl
│ │ └── AECraftingCpuContainerImpl.kt
│ ├── block
│ ├── LateInitSupported.kt
│ ├── WebTerminalBlock.kt
│ ├── entity
│ │ └── WebTerminalBlockEntity.kt
│ └── part
│ │ └── WebTerminalPart.kt
│ ├── client
│ ├── ClientEvents.kt
│ ├── all
│ │ └── Shaders.kt
│ ├── gui
│ │ └── WebTerminalScreen.kt
│ └── rendering
│ │ ├── AEKeyImageProvider.kt
│ │ ├── AEKeyRenderer.kt
│ │ ├── JProgressWindow.kt
│ │ ├── RenderProgressListener.kt
│ │ ├── foundation
│ │ ├── BlurPostProcess.kt
│ │ ├── FrameBuffer.kt
│ │ ├── FullyBufferedBufferSource.kt
│ │ ├── PostProcess.kt
│ │ └── SamplingBlurPostProcess.kt
│ │ └── providers
│ │ ├── AEFluidKeyImageProvider.kt
│ │ └── AEItemKeyImageProvider.kt
│ ├── compat
│ ├── advancedae
│ │ └── AAECraftingCpuContainerImpl.kt
│ ├── appbot
│ │ └── ManaKeyImageProvider.kt
│ ├── appflux
│ │ └── FluxKeyImageProvider.kt
│ ├── appmek
│ │ └── ChemicalKeyImageProvider.kt
│ └── arseng
│ │ └── SourceKeyImageProvider.kt
│ ├── data
│ └── DataGenerator.kt
│ ├── networking
│ ├── OpenWebTerminalScreenPacket.kt
│ └── UpdateWebTerminalNamePacket.kt
│ ├── resource
│ ├── CacheProvider.kt
│ ├── LanguageFileDownloader.kt
│ └── MinecraftVersion.kt
│ ├── support
│ ├── AENetworkAccess.kt
│ ├── AENetworkSupport.kt
│ ├── Data.kt
│ ├── MECraftingServiceView.kt
│ └── http
│ │ ├── Application.kt
│ │ ├── HttpServerLifecycleSupport.kt
│ │ ├── plugins
│ │ ├── HTTP.kt
│ │ ├── Monitoring.kt
│ │ ├── Security.kt
│ │ ├── Serialization.kt
│ │ └── Sockets.kt
│ │ ├── routing
│ │ ├── AEServiceRouting.kt
│ │ ├── FrontendSupportRouting.kt
│ │ ├── Routing.kt
│ │ └── SecuritySupportRouting.kt
│ │ └── websocket
│ │ ├── Protocol.kt
│ │ └── WebsocketSession.kt
│ └── util
│ ├── AEUtil.kt
│ ├── CachedModFile.kt
│ ├── CommandUtils.kt
│ ├── FormattingUtil.kt
│ ├── I18nUtil.kt
│ ├── KRegistrate.kt
│ ├── KubejsI18nSupport.kt
│ ├── LanguageInstance.kt
│ ├── MinecraftI18nSupport.kt
│ ├── QueryJWTAuth.kt
│ ├── RenderingUtil.kt
│ ├── ResourceUtil.kt
│ ├── SerializationUtils.kt
│ ├── ServerI18nSupport.kt
│ └── StateUtils.kt
├── resources
├── META-INF
│ └── versions
│ │ └── 8
│ │ └── DONT_DELETE_ME
├── appwebterminal.mixins.json
├── assets
│ └── appwebterminal
│ │ ├── ae2guide
│ │ ├── structures
│ │ │ └── web_terminal.snbt
│ │ └── web_terminal.md
│ │ ├── lang
│ │ ├── ja_jp.json
│ │ └── zh_cn.json
│ │ ├── models
│ │ ├── block
│ │ │ ├── web_terminal.json
│ │ │ └── web_terminal_offline.json
│ │ ├── item
│ │ │ └── cable_web_terminal.json
│ │ └── part
│ │ │ ├── web_terminal_off.json
│ │ │ └── web_terminal_on.json
│ │ ├── shaders
│ │ └── core
│ │ │ ├── blur.fsh
│ │ │ ├── blur.json
│ │ │ ├── blur.vsh
│ │ │ ├── rendertype_textured_round_rect.fsh
│ │ │ ├── rendertype_textured_round_rect.json
│ │ │ └── rendertype_textured_round_rect.vsh
│ │ └── textures
│ │ ├── block
│ │ ├── web_terminal_bottom.png
│ │ ├── web_terminal_side.png
│ │ ├── web_terminal_top.png
│ │ ├── web_terminal_top.png.mcmeta
│ │ └── web_terminal_top_offline.png
│ │ └── gui
│ │ └── blank.png
└── icon.png
└── templates
├── META-INF
└── mods.toml
└── pack.mcmeta
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Disable autocrlf on generated files, they always generate with LF
2 | # Add any extra files or paths here to make git stop saying they
3 | # are changed when only line endings change.
4 | src/generated/**/.cache/cache text eol=lf
5 | src/generated/**/*.json text eol=lf
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | # from: https://github.com/Fallen-Breath/fabric-mod-template/tree/master/.github/ISSUE_TEMPLATE
2 | name: Bug Report
3 | description: Something doesn't seem correct and it might be a bug
4 | labels: ["🐛bug"]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: Bug description
10 | description: |
11 | A clear and concise description of what the bug is.
12 | Is it a game crash, an unexpected behavior, or has something gone wrong?
13 | If applicable, add screenshots to help explain the bug.
14 | placeholder: Tell us what you see!
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: to-reproduce
19 | attributes:
20 | label: Steps to reproduce
21 | description: Steps to reproduce the bug
22 | placeholder: |
23 | 1. Create a world
24 | 2. Wait until midnight
25 | 3. Hug a creeper
26 | validations:
27 | required: true
28 | - type: textarea
29 | id: expected-behavior
30 | attributes:
31 | label: Expected behavior
32 | description: What did you expect to happen?
33 | placeholder: The creeper explodes
34 | - type: textarea
35 | id: actual-behavior
36 | attributes:
37 | label: Actual behavior
38 | description: What actually happened?
39 | placeholder: The creeper launches itself into the sky
40 | - type: textarea
41 | id: logs
42 | attributes:
43 | label: Relevant logs
44 | description: |-
45 | If it's a crash, send the corresponding Minecraft log in the `logs` folder, or crash report in the `crash-reports` folder, here.
46 | Please upload the log file as an attachment, or upload the log to [pastebin](https://pastebin.com/) / [mclo.gs](https://mclo.gs/) and paste the url here.
47 | Please refrain from pasting the entire log file directly.
48 | Leave empty if there is none.
49 | placeholder: https://pastebin.com/J6b7lKxR
50 | - type: input
51 | id: minecraft-version
52 | attributes:
53 | label: Minecraft version
54 | description: The Minecraft version(s) where this bug occurs in.
55 | placeholder: 1.20.1
56 | validations:
57 | required: true
58 | - type: input
59 | id: mod-version
60 | attributes:
61 | label: TemplateMod version
62 | description: The TemplateMod version(s) where this bug occurs in.
63 | placeholder: 1.1.0
64 | validations:
65 | required: true
66 | - type: textarea
67 | id: other-information
68 | attributes:
69 | label: Other information
70 | description: Other useful information to this bug report, e.g. other related mod version(s). Leave empty if there is none.
71 | placeholder: The issue only occurs if the player is in survival mode
72 | - type: checkboxes
73 | id: check-list
74 | attributes:
75 | label: Check list
76 | options:
77 | - label: I have verified that the issue persists in the latest version of the mod.
78 | required: true
79 | - label: I have searched the existing issues and confirmed that this is not a duplicate.
80 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | # from: https://github.com/Fallen-Breath/fabric-mod-template/tree/master/.github/ISSUE_TEMPLATE
2 | name: Feature Request
3 | description: Suggest an idea for this project
4 | labels: ["💡enhancement"]
5 | body:
6 | - type: textarea
7 | id: motivation
8 | attributes:
9 | label: Motivation
10 | description: Why do you want this feature? What problem do you want to solve? How can the suggested feature help with that?
11 | placeholder: Tell us what you want!
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: description
16 | attributes:
17 | label: Description
18 | description: Describe the feature you want, as detailed as possible
19 | placeholder: I want to see a fancy starry sky when I look up at midnight.
20 | validations:
21 | required: true
22 | - type: textarea
23 | id: other-information
24 | attributes:
25 | label: Other information
26 | description: Other useful information to this feature request. Leave empty if there is none.
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI For AppliedWebTerminal
2 | on:
3 | push:
4 | paths:
5 | - .github/workflows/**
6 | - src/**
7 | - gradle/scripts/**
8 | - build.gradle
9 | - gradle.properties
10 | - settings.gradle
11 | branches:
12 | - main
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 | permissions: write-all
18 | env:
19 | GITHUB_ACTION_NUMBER: ${{ vars.GITHUB_RUN_NUMBER }}
20 | steps:
21 | - name: checkout
22 | uses: actions/checkout@v4
23 |
24 | - name: Setup Java 21
25 | uses: actions/setup-java@v4.2.1
26 | with:
27 | distribution: zulu
28 | java-version: 21
29 |
30 | - name: make gradle wrapper executable
31 | if: ${{ runner.os != 'Windows' }}
32 | run: chmod +x ./gradlew
33 |
34 | - name: Build with Gradle
35 | uses: gradle/actions/setup-gradle@v4.3.1
36 |
37 | - name: Build
38 | run: ./gradlew build
39 |
40 | - name: Read Properties
41 | id: 'properties'
42 | uses: christian-draeger/read-properties@1.1.1
43 | with:
44 | path: gradle.properties
45 | properties: 'mod_id mod_name'
46 |
47 | - name: Get Version
48 | id: var
49 | run: |
50 | MESSAGE=$(ls build/libs/* | grep sources.jar -v | grep dev.jar -v | grep slim.jar -v | awk -F '${{ steps.properties.outputs.mod_id }}-|.jar' '{print $2}')
51 | echo version=$MESSAGE >> $GITHUB_OUTPUT
52 |
53 | - name: Upload a Build Artifact
54 | uses: actions/upload-artifact@v4.3.3
55 | with:
56 | name: "${{ steps.properties.outputs.mod_name }} ${{ steps.var.outputs.version }}"
57 | path: |
58 | build/libs/${{ steps.properties.outputs.mod_id }}-${{ steps.var.outputs.version }}.jar
59 | build/libs/${{ steps.properties.outputs.mod_id }}-${{ steps.var.outputs.version }}-sources.jar
60 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Applied Web Terminal
2 | on:
3 | release:
4 | types:
5 | - published
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | permissions: write-all
11 | steps:
12 | - name: checkout
13 | uses: actions/checkout@v4
14 |
15 | - name: Setup Java 21
16 | uses: actions/setup-java@v4.2.1
17 | with:
18 | distribution: zulu
19 | java-version: 21
20 |
21 | - name: make gradle wrapper executable
22 | if: ${{ runner.os != 'Windows' }}
23 | run: chmod +x ./gradlew
24 |
25 | - name: Build with Gradle
26 | uses: gradle/actions/setup-gradle@v4.3.1
27 |
28 | - name: Build
29 | run: ./gradlew build
30 |
31 | - name: Read Properties
32 | id: 'properties'
33 | uses: christian-draeger/read-properties@1.1.1
34 | with:
35 | path: gradle.properties
36 | properties: 'mod_id mod_name'
37 |
38 | - name: Get Version
39 | id: var
40 | run: |
41 | MESSAGE=$(ls build/libs/* | grep sources.jar -v | grep dev.jar -v | grep slim.jar -v | awk -F '${{ steps.properties.outputs.mod_id }}-|.jar' '{print $2}')
42 | echo version=$MESSAGE >> $GITHUB_OUTPUT
43 |
44 | - name: "publish mc mod"
45 | uses: Kir-Antipov/mc-publish@v3.3.0
46 | with:
47 | name: "${{ steps.properties.outputs.mod_name }} v${{ steps.var.outputs.version }}"
48 | version: ${{ steps.var.outputs.version }}
49 | version-type: release
50 |
51 | github-token: ${{ secrets.GITHUB_TOKEN }}
52 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
53 | curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
54 |
55 | files: |
56 | build/libs/${{ steps.properties.outputs.mod_id }}-${{ steps.var.outputs.version }}.jar
57 | build/libs/${{ steps.properties.outputs.mod_id }}-${{ steps.var.outputs.version }}-dev.jar
58 | build/libs/${{ steps.properties.outputs.mod_id }}-${{ steps.var.outputs.version }}-sources.jar
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # eclipse
2 | bin
3 | *.launch
4 | .settings
5 | .metadata
6 | .classpath
7 | .project
8 |
9 | # idea
10 | out
11 | *.ipr
12 | *.iws
13 | *.iml
14 | .idea
15 |
16 | # gradle
17 | build
18 | .gradle
19 |
20 | # other
21 | eclipse
22 | run
23 | runs
24 | run-data
25 |
26 | repo
27 | .idea/
28 | .kotlin/
29 | .cache/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "frontend"]
2 | path = frontend
3 | url = https://github.com/MercuryGryph/AppliedWebTerminal-WebPanel
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Applied Web Terminal
2 |
3 |
13 |
14 | English | [简体中文](README_zh.md)
15 |
16 | Move your AE terminal to the web!
17 |
18 |
19 | * [Applied Web Terminal](#applied-web-terminal)
20 | * [Dependencies](#dependencies)
21 | * [Usage](#usage)
22 | * [Images](#images)
23 | * [Storage Page](#storage-page)
24 | * [Crafting Status Page](#crafting-status-page)
25 | * [In-Game GUI](#in-game-gui)
26 | * [Configs](#configs)
27 |
28 |
29 | ## Dependencies
30 |
31 | - [AE2](https://modrinth.com/mod/ae2)
32 | - [Kotlin For Forge](https://modrinth.com/mod/kotlin-for-forge)
33 | - [Configuration](https://modrinth.com/mod/configuration)
34 |
35 | ## Usage
36 |
37 | - Download the latest version of this mod from [Releases](https://github.com/ZhuRuoLing/AppliedWebTerminal/releases) and install the dependencies
38 | - If you are playing in single-player mode:
39 | - Enter your single-player world
40 | - Run the command `/appwebterminal resources render` to generate frontend resources (only needs to be run once)
41 | - If you are playing on a server:
42 | - Create a new single-player world
43 | - Run the command `/appwebterminal resources render` to generate frontend resources (only needs to be run once)
44 | - Upload the `aeKeyResources` folder from your game root directory to your server
45 | - Connect the `ME Web Terminal` to the AE network
46 | - Right-click to open the GUI and configure the name and password
47 | - Open your web browser, access the network terminal address (default port `11451`), and log in with the password
48 | - All done!
49 |
50 | ## Images
51 |
52 |
53 | Images
54 |
55 |
56 | ### Storage Page
57 |
58 |
59 |
60 | ### Crafting Status Page
61 |
62 |
63 | ### In-Game GUI
64 |
65 |
66 |
67 |
68 | ## Configs
69 |
70 | Config file is `.minecraft/config/appwebterminal.yaml`
71 |
72 |
73 |
74 | Details
75 |
76 |
77 | > Http Server port for ME Web Terminal.
78 |
79 | `httpPort`: `11451`
80 | - - -
81 | > Web Terminal Title
82 |
83 | `frontendTitle`: `Applied Web Terminal`
84 | - - -
85 | > `Websocket url` for frontend to connect
86 | >
87 | > Use `~` for auto detecting at frontend (use the same host of website)
88 | >
89 | > Example: `ws://example.com/`
90 |
91 | `backendWebsocketEndpoint`: `~`
92 | - - -
93 | > Add PinIn support to languages
94 |
95 | `needPinInLanguage`:
96 |
97 | `- zh_cn`
98 |
99 | `- zh_tw`
100 |
101 | `- zh_hk`
102 | - - -
103 |
104 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # Applied Web Terminal
2 |
3 | [English](README.md) | 简体中文
4 |
5 | 把你的AE终端搬到网页上!
6 |
7 |
8 | * [Applied Web Terminal](#applied-web-terminal)
9 | * [依赖](#依赖)
10 | * [使用](#使用)
11 | * [图片](#图片)
12 | * [存储页面](#存储页面)
13 | * [合成状态页面](#合成状态页面)
14 | * [游戏内GUI](#游戏内gui)
15 | * [配置](#配置)
16 |
17 |
18 | ## 依赖
19 |
20 | - [AE2](https://modrinth.com/mod/ae2)
21 | - [Kotlin For Forge](https://modrinth.com/mod/kotlin-for-forge)
22 | - [Configuration](https://modrinth.com/mod/configuration)
23 |
24 | ## 使用
25 |
26 | - 在[Releases](https://github.com/ZhuRuoLing/AppliedWebTerminal/releases)下载本模组的最新版本 并安装依赖
27 | - 若阁下在游玩单人世界
28 | - 进入您的单人世界
29 | - 运行 `/appwebterminal resources render` 命令以生成前端资源(仅需运行一次)
30 | - 若阁下在游玩服务器
31 | - 新建一个单人世界
32 | - 运行 `/appwebterminal resources render` 命令以生成前端资源(仅需运行一次)
33 | - 将游戏根目录下的 `aeKeyResources` 文件夹上传至您的服务端
34 | - 将 `ME 网络终端` 接入AE网络
35 | - 右键打开gui,并配置名字及密码
36 | - 打开网页浏览器,访问网络终端地址(默认端口`11451`),并输入密码登录
37 | - 大功告成!
38 |
39 | ## 图片
40 |
41 |
42 | 图片
43 |
44 |
45 | ### 存储页面
46 |
47 |
48 |
49 | ### 合成状态页面
50 |
51 |
52 | ### 游戏内GUI
53 |
54 |
55 |
56 |
57 | ## 配置
58 |
59 | 配置文件位于`.minecraft/config/appwebterminal.yaml`
60 |
61 |
62 |
63 | 详细配置
64 |
65 |
66 | > ME Web Terminal 的 Http服务器端口
67 |
68 | `httpPort`: `11451`
69 | - - -
70 | > 前端网页标题
71 |
72 | `frontendTitle`: `Applied Web Terminal`
73 | - - -
74 | > 前端连接的 `Websocket url`
75 | >
76 | > 填入 `~` 则前端自动判断(使用和网页相同的host)
77 | >
78 | > 示例:`ws://example.com/`
79 |
80 | `backendWebsocketEndpoint`: `~`
81 | - - -
82 | > 使用拼音搜索的语言
83 |
84 | `needPinInLanguage`:
85 |
86 | `- zh_cn`
87 |
88 | `- zh_tw`
89 |
90 | `- zh_hk`
91 | - - -
92 |
93 |
--------------------------------------------------------------------------------
/TEMPLATE_LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 NeoForged project
4 |
5 | This license applies to the template files as supplied by github.com/NeoForged/MDK
6 |
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties.
2 | org.gradle.jvmargs=-Xmx1G
3 | org.gradle.daemon=true
4 | org.gradle.parallel=true
5 | org.gradle.caching=true
6 | org.gradle.configuration-cache=true
7 |
8 | #read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment
9 | # you can also find the latest versions at: https://parchmentmc.org/docs/getting-started
10 | parchment_minecraft_version=1.20.1
11 | parchment_mappings_version=2023.09.03
12 | # Environment Properties
13 | # You can find the latest versions here: https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html
14 | # The Minecraft version must agree with the Forge version to get a valid artifact
15 | minecraft_version=1.20.1
16 | # The Minecraft version range can use any release version of Minecraft as bounds.
17 | # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
18 | # as they do not follow standard versioning conventions.
19 | minecraft_version_range=1.20.1
20 | # The Forge version must agree with the Minecraft version to get a valid artifact
21 | forge_version=47.2.0
22 | # The Forge version range can use any version of Forge as bounds
23 | forge_version_range=[47.1.3,)
24 | # The loader version range can only use the major version of FML as bounds
25 | loader_version_range=[4.11,)
26 |
27 | ## Mod Properties
28 |
29 | # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
30 | # Must match the String constant located in the main mod class annotated with @Mod.
31 | mod_id=appwebterminal
32 | # The human-readable display name for the mod.
33 | mod_name=Applied Web Terminal
34 | # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
35 | mod_license=MIT License
36 | # The mod version. See https://semver.org/
37 | mod_version=1.2.3
38 | # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
39 | # This should match the base package used for the mod sources.
40 | # See https://maven.apache.org/guides/mini/guide-naming-conventions.html
41 | mod_group_id=icu.takeneko.appwebterminal
42 | # The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
43 | mod_authors=ZhuRuoLing, DancingSnow0517, MercuryGryph
44 | # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
45 | mod_description=Web Terminal for AppEng2
46 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | registrate = "MC1.20-1.3.11"
3 | jei = "15.20.0.105"
4 | ae2 = "15.3.4"
5 | kotlin-version = "2.1.20"
6 | ktor-version = "3.1.2"
7 |
8 | [libraries]
9 | registrate = { module = "com.tterrag.registrate:Registrate", version.ref = "registrate" }
10 | jei-common-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
11 | jei-forge-api = { module = "mezz.jei:jei-1.20.1-forge-api", version.ref = "jei" }
12 | jei-forge-impl = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
13 | ae2 = { module = "appeng:appliedenergistics2-forge", version.ref = "ae2" }
14 | ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor-version" }
15 | ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor-version" }
16 | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor-version" }
17 | ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor-version" }
18 | ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging", version.ref = "ktor-version" }
19 | ktor-server-forwarded-header = { module = "io.ktor:ktor-server-forwarded-header", version.ref = "ktor-version" }
20 | ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor-version" }
21 | ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers", version.ref = "ktor-version" }
22 | ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor-version" }
23 | ktor-server-auth = { module = "io.ktor:ktor-server-auth", version.ref = "ktor-version" }
24 | ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt", version.ref = "ktor-version" }
25 | ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor-version" }
26 |
27 | [plugins]
28 | ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" }
29 |
30 | [bundles]
31 | jei = ["jei-common-api", "jei-forge-api", "jei-forge-impl"]
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZhuRuoLing/AppliedWebTerminal/18efc779afcba38abb8724079408dd227801eb7d/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.8-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/images/crafting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZhuRuoLing/AppliedWebTerminal/18efc779afcba38abb8724079408dd227801eb7d/images/crafting.png
--------------------------------------------------------------------------------
/images/storage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZhuRuoLing/AppliedWebTerminal/18efc779afcba38abb8724079408dd227801eb7d/images/storage.png
--------------------------------------------------------------------------------
/images/ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZhuRuoLing/AppliedWebTerminal/18efc779afcba38abb8724079408dd227801eb7d/images/ui.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0'
3 | }
4 |
5 | include "frontend"
--------------------------------------------------------------------------------
/src/frontend/frontend/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZhuRuoLing/AppliedWebTerminal/18efc779afcba38abb8724079408dd227801eb7d/src/frontend/frontend/favicon.png
--------------------------------------------------------------------------------
/src/frontend/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Applied Web Terminal
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/frontend/frontend/lang/all.json:
--------------------------------------------------------------------------------
1 | [
2 | "en_us.json",
3 | "zh_cn.json",
4 | "en_ud.json"
5 | ]
--------------------------------------------------------------------------------
/src/frontend/frontend/lang/en_ud.json:
--------------------------------------------------------------------------------
1 | {
2 | "lang.name": "(uʍoᗡ ǝpısd∩)ɥsıןbuƎ",
3 | "common.button.cancel": "ןǝɔuɐƆ",
4 | "common.button.confirm": "ɯɹıɟuoƆ",
5 | "main.terminals.title": "ןɐuıɯɹǝ⟘ Ɐ ʇɔǝןǝS",
6 | "main.terminals.login.title": "ןɐuıɯɹǝ⟘ uıboꞀ",
7 | "main.terminals.login.label.terminal_name": ":ǝɯɐN ןɐuıɯɹǝ⟘",
8 | "main.terminals.login.button.login": "uıboꞀ",
9 | "main.terminals.login.placeholder.password": "pɹoʍssɐԀ",
10 | "main.terminals.login.button.remember_password": "pɹoʍssɐԀ ɹǝqɯǝɯǝᴚ",
11 | "language.selector.title": "ǝbɐnbuɐꞀ",
12 | "language.selector.button.add": "ppⱯ",
13 | "language.add.title": "ǝbɐnbuɐꞀ ppⱯ",
14 | "language.add.placeholder.lang": "ᗡI ǝbɐnbuɐꞀ",
15 | "language.add.label.test": "ʇsǝ⟘",
16 | "language.add.button.test": "ʇsǝ⟘",
17 | "language.add.button.add": "ppⱯ",
18 | "language.add.placeholder.test_key": "(ʎǝʞ)",
19 | "language.add.placeholder.result": "(ʇןnsǝɹ)",
20 | "terminal.button.crafting_page": "snʇɐʇS buıʇɟɐɹƆ",
21 | "terminal.button.storage_page": "ǝbɐɹoʇS",
22 | "terminal.input.search": "˙˙˙ɥɔɹɐǝS",
23 | "ae.crafting.status.missing": "%s :buıssıW",
24 | "ae.crafting.status.available": "%s :ǝןqɐןıɐʌⱯ",
25 | "ae.crafting.status.to_craft": "%s :ʇɟɐɹƆ o⟘",
26 | "ae.crafting.status.crafting": "%s :buıʇɟɐɹƆ",
27 | "ae.crafting.status.scheduled": "%s :pǝןnpǝɥɔS",
28 | "ae.tooltip.stored": "%s :pǝɹoʇS",
29 | "ae.tooltip.craftable": "ǝןqɐʇɟɐɹƆ",
30 | "ae.tooltip.craft": "ʇɟɐɹƆ",
31 | "ae.crafting.cpu": "%d# ∩ԀƆ",
32 | "ae.crafting.status.stored": "%s :pǝɹoʇS",
33 | "ae.crafting.status.active": "%s :buıʇɟɐɹƆ",
34 | "ae.crafting.status.pending": "%s :pǝןnpǝɥɔS",
35 | "websocket.connection.error": "pǝןıɐℲ uoıʇɔǝuuoƆ",
36 | "websocket.connection.established": "pǝɥsıןqɐʇsƎ uoıʇɔǝuuoƆ",
37 | "websocket.connection.disconnected": "ɹǝʌɹǝs ɯoɹɟ pǝʇɔǝuuoɔsıᗡ",
38 | "ae.cpu.storage": "ǝbɐɹoʇS ",
39 | "ae.cpu.coprocessors": "sɹossǝɔoɹԀ-oƆ ",
40 | "ae.cpu.crafting": " buıʇɟɐɹƆ",
41 | "ae.cpu.crafting_progress": " pǝʇɟɐɹƆ",
42 | "ae.cpu.crafting_progress.in": " uı ",
43 | "ae.crafting.plan.title": "uɐןԀ buıʇɟɐɹƆ",
44 | "ae.crafting.plan.title_with_bytes": "pǝs∩ sǝʇʎᗺ %s - uɐןԀ buıʇɟɐɹƆ",
45 | "ae.crafting.plan.calculating": "˙˙˙ʇıɐM ǝsɐǝןԀ buıʇɐןnɔןɐƆ",
46 | "ae.crafting.plan.cancel": "ןǝɔuɐƆ",
47 | "ae.crafting.plan.start": "ʇɹɐʇS",
48 | "ae.crafting.plan.select_amount": "ʇunoɯⱯ ʇɔǝןǝS",
49 | "ae.crafting.plan.next": "ʇxǝN",
50 | "ae.crafting.plan.submit_successes": "ʎןןnɟssǝɔɔnS ʇıɯqnS uɐןԀ ʇɟɐɹƆ",
51 | "ae.crafting.plan.submit_failure": "ǝɹnןıɐℲ ʇıɯqnS uɐןԀ ʇɟɐɹƆ",
52 | "ae.crafting.plan.submit_failed_by": "%s ʎq pǝןıɐɟ ʇıɯqnS uɐןԀ ʇɟɐɹƆ",
53 | "settings.title": "sbuıʇʇǝS",
54 | "settings.item.terminal": "ןɐuıɯɹǝ⟘",
55 | "settings.item.terminal.refresh_interval": "(sʞɔıʇ) ןɐʌɹǝʇuI ɥsǝɹɟǝᴚ",
56 | "settings.item.status": "snʇɐʇS",
57 | "settings.item.language": "ǝbɐnbuɐꞀ",
58 | "settings.item.language.configure": "ǝɹnbıɟuoƆ",
59 | "settings.item.terminal.sorting": "buıʇɹoS",
60 | "settings.item.terminal.order": "ɹǝpɹO",
61 | "settings.item.terminal.sorting.by_name": "ǝɯɐN ɯǝʇI",
62 | "settings.item.terminal.sorting.by_count": "sɯǝʇı ɟo ɹǝqɯnN",
63 | "settings.item.terminal.sorting.by_mod": "poW",
64 | "settings.item.terminal.sorting.ascending": "buıpuǝɔsⱯ",
65 | "settings.item.terminal.sorting.descending": "buıpuǝɔsǝᗡ",
66 | "ae.crafting.sidebar.open": "ɹɐqǝpıS uǝdO",
67 | "ae.crafting.sidebar.close": "ɹɐqǝpıS ǝsdɐןןoƆ"
68 | }
--------------------------------------------------------------------------------
/src/frontend/frontend/lang/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "lang.name": "English (US)",
3 | "common.button.cancel": "Cancel",
4 | "common.button.confirm": "Confirm",
5 | "main.terminals.title": "Select A Terminal",
6 | "main.terminals.login.title": "Login Terminal",
7 | "main.terminals.login.label.terminal_name": "Terminal Name:",
8 | "main.terminals.login.button.login": "Login",
9 | "main.terminals.login.placeholder.password": "Password",
10 | "main.terminals.login.button.remember_password": "Remember Password",
11 | "language.selector.title": "Language",
12 | "language.selector.button.add": "Add",
13 | "language.add.title": "Add Language",
14 | "language.add.placeholder.lang": "Language ID",
15 | "language.add.label.test": "Test",
16 | "language.add.button.test": "Test",
17 | "language.add.button.add": "Add",
18 | "language.add.placeholder.test_key": "(key)",
19 | "language.add.placeholder.result": "(result)",
20 | "terminal.button.crafting_page": "Crafting Status",
21 | "terminal.button.storage_page": "Storage",
22 | "terminal.input.search": "Search...",
23 | "ae.crafting.status.missing": "Missing: %s",
24 | "ae.crafting.status.available": "Available: %s",
25 | "ae.crafting.status.to_craft": "To Craft: %s",
26 | "ae.crafting.status.crafting": "Crafting: %s",
27 | "ae.crafting.status.scheduled": "Scheduled: %s",
28 | "ae.tooltip.stored": "Stored: %s",
29 | "ae.tooltip.craftable": "Craftable",
30 | "ae.tooltip.craft": "Craft",
31 | "ae.crafting.cpu": "CPU #%d",
32 | "ae.crafting.status.stored": "Stored: %s",
33 | "ae.crafting.status.active": "Crafting: %s",
34 | "ae.crafting.status.pending": "Scheduled: %s",
35 | "websocket.connection.error": "Connection Failed",
36 | "websocket.connection.established": "Connection Established",
37 | "websocket.connection.disconnected": "Disconnected from server",
38 | "ae.cpu.storage": " Storage",
39 | "ae.cpu.coprocessors": " Co-Processors",
40 | "ae.cpu.crafting": "Crafting ",
41 | "ae.cpu.crafting_progress": "Crafted ",
42 | "ae.cpu.crafting_progress.in": " in ",
43 | "ae.crafting.plan.title": "Crafting Plan",
44 | "ae.crafting.plan.title_with_bytes": "Crafting Plan - %s Bytes Used",
45 | "ae.crafting.plan.calculating": "Calculating Please Wait...",
46 | "ae.crafting.plan.cancel": "Cancel",
47 | "ae.crafting.plan.start": "Start",
48 | "ae.crafting.plan.select_amount": "Select Amount",
49 | "ae.crafting.plan.next": "Next",
50 | "ae.crafting.plan.submit_successes": "Craft Plan Submit Successfully",
51 | "ae.crafting.plan.submit_failure": "Craft Plan Submit Failure",
52 | "ae.crafting.plan.submit_failed_by": "Craft Plan Submit failed by %s",
53 | "settings.title": "Settings",
54 | "settings.item.terminal": "Terminal",
55 | "settings.item.terminal.refresh_interval": "Refresh Interval (ticks)",
56 | "settings.item.status": "Status",
57 | "settings.item.language": "Language",
58 | "settings.item.language.configure": "Configure",
59 | "settings.item.terminal.sorting": "Sorting",
60 | "settings.item.terminal.order": "Order",
61 | "settings.item.terminal.sorting.by_name":"Item Name",
62 | "settings.item.terminal.sorting.by_count":"Number of items",
63 | "settings.item.terminal.sorting.by_mod":"Mod",
64 | "settings.item.terminal.sorting.ascending": "Ascending",
65 | "settings.item.terminal.sorting.descending": "Descending",
66 | "ae.crafting.sidebar.open": "Open Sidebar",
67 | "ae.crafting.sidebar.close": "Collapse Sidebar"
68 | }
--------------------------------------------------------------------------------
/src/frontend/frontend/lang/zh_cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "lang.name": "简体中文 (中国大陆)",
3 | "common.button.cancel": "取消",
4 | "common.button.confirm": "确认",
5 | "main.terminals.title": "选择终端",
6 | "main.terminals.login.title": "登录终端",
7 | "main.terminals.login.label.terminal_name": "终端:",
8 | "main.terminals.login.button.login": "登录",
9 | "main.terminals.login.placeholder.password": "密码",
10 | "main.terminals.login.button.remember_password": "记住密码",
11 | "language.selector.title": "语言",
12 | "language.selector.button.add": "添加",
13 | "language.add.title": "添加语言",
14 | "language.add.placeholder.lang": "语言 ID",
15 | "language.add.label.test": "测试",
16 | "language.add.button.test": "测试",
17 | "language.add.button.add": "添加",
18 | "language.add.placeholder.test_key": "(key)",
19 | "language.add.placeholder.result": "(结果)",
20 | "terminal.button.crafting_page": "合成状态",
21 | "terminal.button.storage_page": "存储",
22 | "terminal.input.search": "搜索...",
23 | "ae.crafting.status.missing": "缺少数量:%s",
24 | "ae.crafting.status.available": "可用数量:%s",
25 | "ae.crafting.status.to_craft": "合成数量:%s",
26 | "ae.crafting.status.crafting": "正在合成:%s",
27 | "ae.crafting.status.scheduled": "计划合成:%s",
28 | "ae.tooltip.stored": "已存储:%s",
29 | "ae.tooltip.craftable": "可合成",
30 | "ae.tooltip.craft": "合成",
31 | "ae.crafting.status.stored": "可用数量: %s",
32 | "ae.crafting.status.active": "正在合成: %s",
33 | "ae.crafting.status.pending": "计划合成: %s",
34 | "websocket.connection.error": "连接失败",
35 | "websocket.connection.established": "已连接",
36 | "websocket.connection.disconnected": "连接已断开",
37 | "ae.cpu.storage": " 存储容量",
38 | "ae.cpu.coprocessors": " 并行处理单元",
39 | "ae.cpu.crafting": "正在合成",
40 | "ae.cpu.crafting_progress": "已合成 ",
41 | "ae.crafting.cpu": "处理器 #%d",
42 | "ae.cpu.crafting_progress.in": ",用时 ",
43 | "ae.crafting.plan.title": "合成计划",
44 | "ae.crafting.plan.title_with_bytes": "合成计划 - %s 字节已使用",
45 | "ae.crafting.plan.calculating": "计算中,请稍后...",
46 | "ae.crafting.plan.cancel": "取消",
47 | "ae.crafting.plan.start": "开始",
48 | "ae.crafting.plan.select_amount": "选择数量",
49 | "ae.crafting.plan.next": "下一步",
50 | "ae.crafting.plan.submit_successes": "合成计划提交成功",
51 | "ae.crafting.plan.submit_failure": "合成计划提交失败",
52 | "ae.crafting.plan.submit_failed_by": "由于 %s,合成计划提交失败",
53 | "settings.title": "设置",
54 | "settings.item.terminal": "终端",
55 | "settings.item.terminal.refresh_interval": "更新间隔",
56 | "settings.item.status": "状态",
57 | "settings.item.language": "语言",
58 | "settings.item.language.configure": "配置",
59 | "ae.crafting.sidebar.open": "展开侧边栏",
60 | "ae.crafting.sidebar.close": "折叠侧边栏",
61 | "settings.item.terminal.sorting": "排序",
62 | "settings.item.terminal.sorting.ascending": "增序",
63 | "settings.item.terminal.sorting.descending": "降序",
64 | "settings.item.terminal.order": "排序顺序",
65 | "settings.item.terminal.sorting.by_name": "物品名称",
66 | "settings.item.terminal.sorting.by_count": "物品数量",
67 | "settings.item.terminal.sorting.by_mod": "模组"
68 | }
--------------------------------------------------------------------------------
/src/generated/resources/assets/appwebterminal/blockstates/web_terminal.json:
--------------------------------------------------------------------------------
1 | {
2 | "variants": {
3 | "facing=down,online=false": {
4 | "model": "appwebterminal:block/web_terminal_offline",
5 | "x": 180
6 | },
7 | "facing=down,online=true": {
8 | "model": "appwebterminal:block/web_terminal",
9 | "x": 180
10 | },
11 | "facing=east,online=false": {
12 | "model": "appwebterminal:block/web_terminal_offline",
13 | "x": 90,
14 | "y": 90
15 | },
16 | "facing=east,online=true": {
17 | "model": "appwebterminal:block/web_terminal",
18 | "x": 90,
19 | "y": 90
20 | },
21 | "facing=north,online=false": {
22 | "model": "appwebterminal:block/web_terminal_offline",
23 | "x": 90
24 | },
25 | "facing=north,online=true": {
26 | "model": "appwebterminal:block/web_terminal",
27 | "x": 90
28 | },
29 | "facing=south,online=false": {
30 | "model": "appwebterminal:block/web_terminal_offline",
31 | "x": 90,
32 | "y": 180
33 | },
34 | "facing=south,online=true": {
35 | "model": "appwebterminal:block/web_terminal",
36 | "x": 90,
37 | "y": 180
38 | },
39 | "facing=up,online=false": {
40 | "model": "appwebterminal:block/web_terminal_offline"
41 | },
42 | "facing=up,online=true": {
43 | "model": "appwebterminal:block/web_terminal"
44 | },
45 | "facing=west,online=false": {
46 | "model": "appwebterminal:block/web_terminal_offline",
47 | "x": 90,
48 | "y": 270
49 | },
50 | "facing=west,online=true": {
51 | "model": "appwebterminal:block/web_terminal",
52 | "x": 90,
53 | "y": 270
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/generated/resources/assets/appwebterminal/lang/en_ud.json:
--------------------------------------------------------------------------------
1 | {
2 | "appwebterminal.button.done": "ǝuoᗡ",
3 | "appwebterminal.gui.me_network_offline": "ǝuıןɟɟO",
4 | "appwebterminal.gui.me_network_online": "ǝuıןuO",
5 | "appwebterminal.hint.name": " :ǝɯɐN",
6 | "appwebterminal.hint.password": " :pɹoʍssɐԀ",
7 | "appwebterminal.message.render_complete": "˙ǝʇǝןdɯoɔ ɹǝpuǝᴚ",
8 | "appwebterminal.message.rendering": "˙buıʞɹoʍ ʎןʇuǝɹɹnɔ sı ɹǝɹǝpuǝɹ Ɐ",
9 | "appwebterminal.message.started": "˙ɹǝɹǝpuǝɹ pǝʇɹɐʇS",
10 | "appwebterminal.screen.title": "ןɐuıɯɹǝ⟘ qǝM ƎW",
11 | "block.appwebterminal.web_terminal": "ןɐuıɯɹǝ⟘ qǝM ƎW",
12 | "config.appwebterminal.option.assetsSource": "ǝɔɹnoS sʇǝssⱯ",
13 | "config.appwebterminal.option.backendWebsocketEndpoint": "ʇuıodpuƎ ʇǝʞɔosqǝM puǝʞɔɐᗺ",
14 | "config.appwebterminal.option.frontendTitle": "ǝןʇı⟘ puǝʇuoɹℲ",
15 | "config.appwebterminal.option.httpPort": "ʇɹoԀ dʇʇH",
16 | "config.appwebterminal.option.needPinInLanguage": "ǝbɐnbuɐꞀ uI uıԀ pǝǝN",
17 | "config.screen.appwebterminal": "sbuıʇʇǝS ןɐuıɯɹǝ⟘qǝMpǝıןddⱯ",
18 | "item.appwebterminal.cable_web_terminal": "ןɐuıɯɹǝ⟘ qǝM ƎW",
19 | "itemGroup.appwebterminal.applied_web_terminal": "ןɐuıɯɹǝ⟘ qǝM pǝıןddⱯ"
20 | }
--------------------------------------------------------------------------------
/src/generated/resources/assets/appwebterminal/lang/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "appwebterminal.button.done": "Done",
3 | "appwebterminal.gui.me_network_offline": "Offline",
4 | "appwebterminal.gui.me_network_online": "Online",
5 | "appwebterminal.hint.name": "Name: ",
6 | "appwebterminal.hint.password": "Password: ",
7 | "appwebterminal.message.render_complete": "Render complete.",
8 | "appwebterminal.message.rendering": "A renderer is currently working.",
9 | "appwebterminal.message.started": "Started renderer.",
10 | "appwebterminal.screen.title": "ME Web Terminal",
11 | "block.appwebterminal.web_terminal": "ME Web Terminal",
12 | "config.appwebterminal.option.assetsSource": "Assets Source",
13 | "config.appwebterminal.option.backendWebsocketEndpoint": "Backend Websocket Endpoint",
14 | "config.appwebterminal.option.frontendTitle": "Frontend Title",
15 | "config.appwebterminal.option.httpPort": "Http Port",
16 | "config.appwebterminal.option.needPinInLanguage": "Need Pin In Language",
17 | "config.screen.appwebterminal": "AppliedWebTerminal Settings",
18 | "item.appwebterminal.cable_web_terminal": "ME Web Terminal",
19 | "itemGroup.appwebterminal.applied_web_terminal": "Applied Web Terminal"
20 | }
--------------------------------------------------------------------------------
/src/generated/resources/assets/appwebterminal/models/item/web_terminal.json:
--------------------------------------------------------------------------------
1 | {
2 | "parent": "appwebterminal:block/web_terminal"
3 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/advancements/recipes/misc/cable_web_terminal_from_block.json:
--------------------------------------------------------------------------------
1 | {
2 | "parent": "minecraft:recipes/root",
3 | "criteria": {
4 | "has_item": {
5 | "conditions": {
6 | "items": [
7 | {
8 | "items": [
9 | "appwebterminal:web_terminal"
10 | ]
11 | }
12 | ]
13 | },
14 | "trigger": "minecraft:inventory_changed"
15 | },
16 | "has_the_recipe": {
17 | "conditions": {
18 | "recipe": "appwebterminal:cable_web_terminal_from_block"
19 | },
20 | "trigger": "minecraft:recipe_unlocked"
21 | }
22 | },
23 | "requirements": [
24 | [
25 | "has_item",
26 | "has_the_recipe"
27 | ]
28 | ],
29 | "rewards": {
30 | "recipes": [
31 | "appwebterminal:cable_web_terminal_from_block"
32 | ]
33 | },
34 | "sends_telemetry_event": false
35 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/advancements/recipes/misc/cable_web_terminal_from_part.json:
--------------------------------------------------------------------------------
1 | {
2 | "parent": "minecraft:recipes/root",
3 | "criteria": {
4 | "has_item": {
5 | "conditions": {
6 | "items": [
7 | {
8 | "items": [
9 | "appwebterminal:cable_web_terminal"
10 | ]
11 | }
12 | ]
13 | },
14 | "trigger": "minecraft:inventory_changed"
15 | },
16 | "has_the_recipe": {
17 | "conditions": {
18 | "recipe": "appwebterminal:cable_web_terminal_from_part"
19 | },
20 | "trigger": "minecraft:recipe_unlocked"
21 | }
22 | },
23 | "requirements": [
24 | [
25 | "has_item",
26 | "has_the_recipe"
27 | ]
28 | ],
29 | "rewards": {
30 | "recipes": [
31 | "appwebterminal:cable_web_terminal_from_part"
32 | ]
33 | },
34 | "sends_telemetry_event": false
35 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/advancements/recipes/misc/web_terminal.json:
--------------------------------------------------------------------------------
1 | {
2 | "parent": "minecraft:recipes/root",
3 | "criteria": {
4 | "has_item": {
5 | "conditions": {
6 | "items": [
7 | {
8 | "items": [
9 | "ae2:wireless_access_point"
10 | ]
11 | }
12 | ]
13 | },
14 | "trigger": "minecraft:inventory_changed"
15 | },
16 | "has_the_recipe": {
17 | "conditions": {
18 | "recipe": "appwebterminal:web_terminal"
19 | },
20 | "trigger": "minecraft:recipe_unlocked"
21 | }
22 | },
23 | "requirements": [
24 | [
25 | "has_item",
26 | "has_the_recipe"
27 | ]
28 | ],
29 | "rewards": {
30 | "recipes": [
31 | "appwebterminal:web_terminal"
32 | ]
33 | },
34 | "sends_telemetry_event": false
35 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/loot_tables/blocks/web_terminal.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "minecraft:block",
3 | "pools": [
4 | {
5 | "bonus_rolls": 0.0,
6 | "conditions": [
7 | {
8 | "condition": "minecraft:survives_explosion"
9 | }
10 | ],
11 | "entries": [
12 | {
13 | "type": "minecraft:item",
14 | "name": "appwebterminal:web_terminal"
15 | }
16 | ],
17 | "rolls": 1.0
18 | }
19 | ],
20 | "random_sequence": "appwebterminal:blocks/web_terminal"
21 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/recipes/cable_web_terminal_from_block.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "minecraft:crafting_shapeless",
3 | "category": "misc",
4 | "ingredients": [
5 | {
6 | "item": "appwebterminal:web_terminal"
7 | }
8 | ],
9 | "result": {
10 | "item": "appwebterminal:cable_web_terminal"
11 | }
12 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/recipes/cable_web_terminal_from_part.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "minecraft:crafting_shapeless",
3 | "category": "misc",
4 | "ingredients": [
5 | {
6 | "item": "appwebterminal:cable_web_terminal"
7 | }
8 | ],
9 | "result": {
10 | "item": "appwebterminal:web_terminal"
11 | }
12 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/appwebterminal/recipes/web_terminal.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "minecraft:crafting_shaped",
3 | "category": "misc",
4 | "key": {
5 | "A": {
6 | "item": "ae2:charged_certus_quartz_crystal"
7 | },
8 | "B": {
9 | "item": "ae2:wireless_access_point"
10 | },
11 | "C": {
12 | "item": "ae2:engineering_processor"
13 | },
14 | "D": {
15 | "item": "ae2:crafting_card"
16 | },
17 | "E": {
18 | "item": "ae2:quartz_vibrant_glass"
19 | },
20 | "F": {
21 | "item": "ae2:crafting_monitor"
22 | }
23 | },
24 | "pattern": [
25 | "ABA",
26 | "CDC",
27 | "EFE"
28 | ],
29 | "result": {
30 | "item": "appwebterminal:web_terminal"
31 | },
32 | "show_notification": true
33 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/minecraft/tags/blocks/mineable/pickaxe.json:
--------------------------------------------------------------------------------
1 | {
2 | "values": [
3 | "appwebterminal:web_terminal"
4 | ]
5 | }
--------------------------------------------------------------------------------
/src/generated/resources/data/minecraft/tags/blocks/needs_stone_tool.json:
--------------------------------------------------------------------------------
1 | {
2 | "values": [
3 | "appwebterminal:web_terminal"
4 | ]
5 | }
--------------------------------------------------------------------------------
/src/main/java/icu/takeneko/appwebterminal/api/ImageProvider.java:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.api;
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 | @Target(ElementType.TYPE)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface ImageProvider {
11 | String value();
12 | String modid();
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/icu/takeneko/appwebterminal/config/AppWebTerminalConfig.java:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.config;
2 |
3 | import dev.toma.configuration.config.Config;
4 | import dev.toma.configuration.config.Configurable;
5 | import dev.toma.configuration.config.UpdateRestrictions;
6 | import icu.takeneko.appwebterminal.AppWebTerminal;
7 |
8 | @Config(id = AppWebTerminal.MOD_ID)
9 | public class AppWebTerminalConfig {
10 | @Configurable
11 | @Configurable.Comment({"Http Server port for ME Web Terminal."})
12 | @Configurable.Range(
13 | min = 1L,
14 | max = 65536L
15 | )
16 | @Configurable.UpdateRestriction(UpdateRestrictions.MAIN_MENU)
17 | public int httpPort = 11451;
18 |
19 | @Configurable
20 | @Configurable.Comment({"Web Terminal Title"})
21 | public String frontendTitle = "Applied Web Terminal";
22 |
23 | @Configurable
24 | @Configurable.Comment({"Websocket url for frontend to connect", "Use \"~\" for auto detecting"})
25 | public String backendWebsocketEndpoint = "~";
26 |
27 | @Configurable
28 | @Configurable.Comment({"Add PinIn support to languages"})
29 | public String[] needPinInLanguage = {"zh_cn", "zh_tw", "zh_hk"};
30 |
31 | @Configurable
32 | @Configurable.Comment({"Used api to download minecraft langague files"})
33 | @Configurable.UpdateRestriction(UpdateRestrictions.GAME_RESTART)
34 | public MinecraftAssetsApi assetsSource = MinecraftAssetsApi.MOJANG;
35 |
36 | @SuppressWarnings("unused")
37 | public int getHttpPort() {
38 | return httpPort;
39 | }
40 |
41 | @SuppressWarnings("unused")
42 | public String getFrontendTitle() {
43 | return frontendTitle;
44 | }
45 |
46 | @SuppressWarnings("unused")
47 | public String getBackendWebsocketEndpoint() {
48 | return backendWebsocketEndpoint;
49 | }
50 |
51 | @SuppressWarnings("unused")
52 | public String[] getNeedPinInLanguage() {
53 | return needPinInLanguage;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/icu/takeneko/appwebterminal/config/MinecraftAssetsApi.java:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.config;
2 |
3 | import java.util.function.Function;
4 |
5 | public enum MinecraftAssetsApi {
6 | MOJANG(
7 | "https://piston-meta.mojang.com",
8 | Function.identity(),
9 | Function.identity(),
10 | Function.identity()
11 | ),
12 | BMCLAPI(
13 | "https://bmclapi2.bangbang93.com",
14 | s -> s.replace("https://piston-meta.mojang.com", "https://piston-meta.bmclapi.com"),
15 | s -> s.replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com"),
16 | s -> s.replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets")
17 | );
18 |
19 | private final String endpoint;
20 | private final Function urlManifestReplacer;
21 | private final Function urlIndexReplacer;
22 | private final Function urlAssetsReplacer;
23 |
24 | MinecraftAssetsApi(String endpoint, Function urlManifestReplacer, Function urlIndexReplacer, Function urlAssetsReplacer) {
25 | this.endpoint = endpoint;
26 | this.urlManifestReplacer = urlManifestReplacer;
27 | this.urlIndexReplacer = urlIndexReplacer;
28 | this.urlAssetsReplacer = urlAssetsReplacer;
29 | }
30 |
31 | public String getEndpoint() {
32 | return endpoint;
33 | }
34 |
35 | public Function getUrlManifestReplacer() {
36 | return urlManifestReplacer;
37 | }
38 |
39 | public Function getUrlIndexReplacer() {
40 | return urlIndexReplacer;
41 | }
42 |
43 | public Function getUrlAssetsReplacer() {
44 | return urlAssetsReplacer;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/icu/takeneko/appwebterminal/mixins/LevelRendererMixin.java:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.mixins;
2 |
3 | import icu.takeneko.appwebterminal.client.all.ShadersKt;
4 | import icu.takeneko.appwebterminal.client.rendering.foundation.PostProcess;
5 | import net.minecraft.client.renderer.LevelRenderer;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Inject;
9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 |
11 | @Mixin(LevelRenderer.class)
12 | public class LevelRendererMixin {
13 | @Inject(method = "resize", at = @At("HEAD"))
14 | private void onResize(int width, int height, CallbackInfo ci) {
15 | PostProcess instance = ShadersKt.getBlurPostProcessInstance();
16 | if (instance != null) {
17 | instance.resize(width, height);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/icu/takeneko/appwebterminal/mixins/RenderStateShardMixin.java:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.mixins;
2 |
3 | import icu.takeneko.appwebterminal.client.rendering.AEKeyRenderer;
4 | import net.minecraft.client.renderer.RenderStateShard;
5 | import org.spongepowered.asm.mixin.Mixin;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 |
10 | @Mixin(RenderStateShard.class)
11 | public class RenderStateShardMixin {
12 | @Inject(
13 | method = "setupRenderState",
14 | at = @At("HEAD"),
15 | cancellable = true
16 | )
17 | private void invalidateOutputStateShardSetup(CallbackInfo ci) {
18 | //noinspection ConstantValue
19 | if ((Object) this instanceof RenderStateShard.OutputStateShard && AEKeyRenderer.Companion.getRendering()) {
20 | ci.cancel();
21 | }
22 | }
23 |
24 | @Inject(
25 | method = "clearRenderState",
26 | at = @At("HEAD"),
27 | cancellable = true
28 | )
29 | private void invalidateOutputStateShardClear(CallbackInfo ci) {
30 | //noinspection ConstantValue
31 | if ((Object) this instanceof RenderStateShard.OutputStateShard && AEKeyRenderer.Companion.getRendering()) {
32 | ci.cancel();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/AppWebTerminal.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal
2 |
3 | import com.mojang.logging.LogUtils
4 | import dev.toma.configuration.Configuration
5 | import dev.toma.configuration.config.format.ConfigFormats
6 | import icu.takeneko.appwebterminal.all.onAddRegistries
7 | import icu.takeneko.appwebterminal.all.onChunkUnloaded
8 | import icu.takeneko.appwebterminal.all.onCommonSetup
9 | import icu.takeneko.appwebterminal.all.onRegister
10 | import icu.takeneko.appwebterminal.all.onServerStart
11 | import icu.takeneko.appwebterminal.all.onServerStop
12 | import icu.takeneko.appwebterminal.all.onServerTickPost
13 | import icu.takeneko.appwebterminal.all.registerBlockEntities
14 | import icu.takeneko.appwebterminal.all.registerBlocks
15 | import icu.takeneko.appwebterminal.all.registerClientCommand
16 | import icu.takeneko.appwebterminal.all.registerCreativeTab
17 | import icu.takeneko.appwebterminal.all.registerItems
18 | import icu.takeneko.appwebterminal.all.registerKeyImageProviders
19 | import icu.takeneko.appwebterminal.all.registerNetworking
20 | import icu.takeneko.appwebterminal.api.KeyImageProviderLoader
21 | import icu.takeneko.appwebterminal.api.crafting.impl.AECraftingCpuContainerImpl
22 | import icu.takeneko.appwebterminal.client.onLoadShaders
23 | import icu.takeneko.appwebterminal.client.onRenderLevelPost
24 | import icu.takeneko.appwebterminal.compat.advancedae.registerAAECompat
25 | import icu.takeneko.appwebterminal.config.AppWebTerminalConfig
26 | import icu.takeneko.appwebterminal.data.configureDataGeneration
27 | import icu.takeneko.appwebterminal.resource.LanguageFileDownloader
28 | import icu.takeneko.appwebterminal.util.KRegistrate
29 | import net.minecraft.resources.ResourceLocation
30 | import net.minecraftforge.api.distmarker.Dist
31 | import net.minecraftforge.common.MinecraftForge
32 | import net.minecraftforge.eventbus.api.IEventBus
33 | import net.minecraftforge.fml.common.Mod
34 | import org.slf4j.Logger
35 | import thedarkcolour.kotlinforforge.forge.FORGE_BUS
36 | import thedarkcolour.kotlinforforge.forge.MOD_BUS
37 | import thedarkcolour.kotlinforforge.forge.runWhenOn
38 |
39 | val registrate = KRegistrate.create(AppWebTerminal.MOD_ID)
40 |
41 |
42 | @Mod(AppWebTerminal.MOD_ID)
43 | object AppWebTerminal {
44 | const val MOD_ID = "appwebterminal"
45 | private val LOGGER: Logger = LogUtils.getLogger()
46 | val modBus: IEventBus = MOD_BUS
47 | internal val configHolder = Configuration.registerConfig(AppWebTerminalConfig::class.java, ConfigFormats.YAML)
48 | val config: AppWebTerminalConfig
49 | get() = configHolder.configInstance
50 |
51 | fun location(location: String): ResourceLocation = ResourceLocation(MOD_ID, location)
52 |
53 | init {
54 | registerBlockEntities()
55 | registerBlocks()
56 | registerItems()
57 | registerCreativeTab()
58 | registerNetworking()
59 | configureDataGeneration()
60 | registerKeyImageProviders(modBus)
61 | modBus.addListener(::onCommonSetup)
62 | modBus.addListener(::onAddRegistries)
63 | modBus.addListener(::onRegister)
64 | MinecraftForge.EVENT_BUS.addListener(::onServerStart)
65 | MinecraftForge.EVENT_BUS.addListener(::onServerStop)
66 | MinecraftForge.EVENT_BUS.addListener(::onChunkUnloaded)
67 | MinecraftForge.EVENT_BUS.addListener(::onServerTickPost)
68 | LanguageFileDownloader(config.assetsSource).start()
69 | KeyImageProviderLoader.compileContents()
70 | LOGGER.info("AppWebTerminal initialized")
71 | AECraftingCpuContainerImpl.register()
72 | registerAAECompat()
73 | runWhenOn(Dist.CLIENT) {
74 | FORGE_BUS.addListener(::registerClientCommand)
75 | FORGE_BUS.addListener(::onRenderLevelPost)
76 | modBus.addListener(::onLoadShaders)
77 | }
78 | }
79 |
80 |
81 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/BlockEntities.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import com.tterrag.registrate.util.entry.BlockEntityEntry
4 | import icu.takeneko.appwebterminal.block.entity.WebTerminalBlockEntity
5 | import icu.takeneko.appwebterminal.registrate
6 |
7 | val MEWebTerminalBlockEntity: BlockEntityEntry =
8 | registrate.blockEntity("web_terminal", ::WebTerminalBlockEntity)
9 | .validBlock(MEWebTerminal)
10 | .register()
11 |
12 | fun registerBlockEntities() {
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Blocks.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import appeng.core.definitions.AEBlocks
4 | import appeng.core.definitions.AEItems
5 | import com.tterrag.registrate.providers.RegistrateRecipeProvider
6 | import com.tterrag.registrate.util.entry.BlockEntry
7 | import icu.takeneko.appwebterminal.AppWebTerminal
8 | import icu.takeneko.appwebterminal.block.WebTerminalBlock
9 | import icu.takeneko.appwebterminal.registrate
10 | import icu.takeneko.appwebterminal.util.get
11 | import net.minecraft.data.recipes.RecipeCategory
12 | import net.minecraft.data.recipes.ShapedRecipeBuilder
13 | import net.minecraft.data.recipes.ShapelessRecipeBuilder
14 | import net.minecraft.tags.BlockTags
15 | import net.minecraft.world.level.block.Blocks
16 | import net.minecraftforge.client.model.generators.ModelFile.UncheckedModelFile
17 |
18 | val MEWebTerminal: BlockEntry = registrate.block("web_terminal", ::WebTerminalBlock)
19 | .initialProperties { Blocks.IRON_BLOCK }
20 | .blockstate { dataGenContext, registrateBlockstateProvider ->
21 | registrateBlockstateProvider.directionalBlock(dataGenContext.get()) {
22 | UncheckedModelFile(
23 | AppWebTerminal.location(
24 | if (it[WebTerminalBlock.ONLINE])
25 | "block/web_terminal"
26 | else
27 | "block/web_terminal_offline"
28 | )
29 | )
30 | }
31 | }
32 | .lang("ME Web Terminal")
33 | .properties {
34 | it.lightLevel { bs ->
35 | if (bs[WebTerminalBlock.ONLINE]) 5 else 0
36 | }
37 | }
38 | .tag(BlockTags.MINEABLE_WITH_PICKAXE)
39 | .tag(BlockTags.NEEDS_STONE_TOOL)
40 | .item()
41 | .model { dataGenContext, registrateItemModelProvider ->
42 | registrateItemModelProvider.getBuilder(dataGenContext.name)
43 | .parent(UncheckedModelFile(AppWebTerminal.location("block/web_terminal")))
44 | }
45 | .recipe { ctx, prov ->
46 | ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ctx.get())
47 | .pattern("ABA")
48 | .pattern("CDC")
49 | .pattern("EFE")
50 | .define('A', AEItems.CERTUS_QUARTZ_CRYSTAL_CHARGED)
51 | .define('B', AEBlocks.WIRELESS_ACCESS_POINT)
52 | .define('C', AEItems.ENGINEERING_PROCESSOR)
53 | .define('D', AEItems.CRAFTING_CARD)
54 | .define('E', AEBlocks.QUARTZ_VIBRANT_GLASS)
55 | .define('F', AEBlocks.CRAFTING_MONITOR)
56 | .unlockedBy("has_item", RegistrateRecipeProvider.has(AEBlocks.WIRELESS_ACCESS_POINT))
57 | .save(prov)
58 | ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, ctx.get())
59 | .requires(MEWebTerminalPartItem)
60 | .unlockedBy("has_item", RegistrateRecipeProvider.has(MEWebTerminalPartItem))
61 | .save(prov, AppWebTerminal.location("cable_web_terminal_from_part"))
62 | }
63 | .build()
64 | .register()
65 |
66 |
67 | fun registerBlocks() {
68 | //intentionally empty
69 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Commands.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import appeng.api.stacks.AEKey
4 | import icu.takeneko.appwebterminal.client.rendering.AEKeyRenderer
5 | import icu.takeneko.appwebterminal.client.rendering.JProgressWindow
6 | import icu.takeneko.appwebterminal.client.rendering.RenderProgressListener
7 | import icu.takeneko.appwebterminal.util.CommandContext
8 | import icu.takeneko.appwebterminal.util.LiteralCommand
9 | import icu.takeneko.appwebterminal.util.S
10 | import icu.takeneko.appwebterminal.util.execute
11 | import icu.takeneko.appwebterminal.util.integerArgument
12 | import icu.takeneko.appwebterminal.util.literal
13 | import icu.takeneko.appwebterminal.util.sendError
14 | import icu.takeneko.appwebterminal.util.sendFeedback
15 | import net.minecraft.client.Minecraft
16 | import net.minecraft.commands.CommandSourceStack
17 | import net.minecraft.network.chat.Component
18 | import net.minecraftforge.client.event.RegisterClientCommandsEvent
19 | import org.slf4j.LoggerFactory
20 | import java.awt.HeadlessException
21 | import kotlin.io.path.Path
22 |
23 | val AppWebTerminalCommand = LiteralCommand("appwebterminal") {
24 | literal("resources") {
25 | literal("upload") {
26 | execute {
27 | val src: CommandSourceStack by this
28 | if (Minecraft.getInstance().isLocalServer) {
29 | sendError(Component.translatable("appwebterminal.message.join_server_required"))
30 | return@execute 1
31 | }
32 |
33 | 0
34 | }
35 | }
36 | literal("render") {
37 | integerArgument("limit") {
38 | integerArgument("size") {
39 | execute {
40 | val limit: Int by this
41 | val size: Int by this
42 | executeRender(limit, size)
43 | }
44 | }
45 | execute {
46 | val limit: Int by this
47 | executeRender(limit, 256)
48 | }
49 | }
50 | execute {
51 | executeRender(1, 256)
52 | }
53 | }
54 | literal("listRegistered") {
55 | execute {
56 | KeyImageProviderRegistry.keys.forEach {
57 | sendFeedback(Component.literal(it.toString()))
58 | }
59 | return@execute 1
60 | }
61 | }
62 | }
63 | }
64 |
65 | private class ProgressListenerImpl(private val src: CommandSourceStack) : RenderProgressListener {
66 | private var total: Int = 0
67 | private val logger = LoggerFactory.getLogger("RenderProgress")
68 | val progressWindow: JProgressWindow? = try {
69 | JProgressWindow("Rendering Resources")
70 | } catch (_: HeadlessException) {
71 | null
72 | }
73 |
74 | override fun notifyTotalCount(size: Int) {
75 | logger.info("Total: $size")
76 | src.sendSystemMessage(Component.literal("Total: $size"))
77 | total = size
78 | progressWindow?.notifyTotalCount(size)
79 | }
80 |
81 | override fun notifyProgress(current: Int, what: AEKey) {
82 | logger.info("Progress: $current/$total, Current: ${what.type}/${what.id}")
83 | src.sendSystemMessage(Component.literal("Progress: $current/$total, Current: ${what.type}/${what.id}"))
84 | progressWindow?.notifyProgress(current, what)
85 | }
86 |
87 | override fun notifyCompleted() {
88 | logger.info("Render Completed")
89 | progressWindow?.notifyCompleted()
90 | }
91 | }
92 |
93 | fun registerClientCommand(event: RegisterClientCommandsEvent) {
94 | event.dispatcher.register(AppWebTerminalCommand.node)
95 | }
96 |
97 | fun CommandContext.executeRender(limit: Int, size: Int): Int {
98 | if (AEKeyRenderer.instance != null) {
99 | sendError(Component.translatable("appwebterminal.message.rendering"))
100 | return 1
101 | }
102 | System.setProperty("java.awt.headless", "false")
103 | val progressListener = ProgressListenerImpl(this.delegate.source)
104 | progressListener.progressWindow?.show()
105 | val renderer = AEKeyRenderer(size, size).apply {
106 | submitRenderTasks(Path("./aeKeyResources"), progressListener)
107 | }
108 | renderer.taskPullLimit = limit
109 | renderer.renderTasks += {
110 | sendFeedback(Component.translatable("appwebterminal.message.render_complete"))
111 | renderer.dispose()
112 | AEKeyRenderer.instance = null
113 | }
114 | AEKeyRenderer.instance = renderer
115 | progressListener.progressWindow?.dismiss()
116 | sendFeedback(Component.translatable("appwebterminal.message.started"))
117 | return 1
118 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/CreativeTabs.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import icu.takeneko.appwebterminal.registrate
4 |
5 | val appWebCreativeTab = registrate
6 | .defaultCreativeTab("applied_web_terminal") {
7 | it.icon(MEWebTerminal::asStack)
8 | it.displayItems { parameters, output ->
9 | output.accept(MEWebTerminal)
10 | output.accept(MEWebTerminalPartItem)
11 | }
12 | }
13 | .register()
14 |
15 | fun registerCreativeTab() {
16 |
17 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Events.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import icu.takeneko.appwebterminal.AppWebTerminal
4 | import icu.takeneko.appwebterminal.api.KeyImageProviderLoader
5 | import icu.takeneko.appwebterminal.block.LateInitSupported
6 | import icu.takeneko.appwebterminal.block.entity.WebTerminalBlockEntity
7 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
8 | import icu.takeneko.appwebterminal.registrate
9 | import icu.takeneko.appwebterminal.support.AENetworkSupport
10 | import icu.takeneko.appwebterminal.support.http.HttpServerLifecycleSupport
11 | import icu.takeneko.appwebterminal.util.KubejsI18nSupport
12 | import net.minecraft.core.registries.Registries
13 | import net.minecraft.server.level.ServerLevel
14 | import net.minecraft.world.level.chunk.LevelChunk
15 | import net.minecraftforge.event.TickEvent
16 | import net.minecraftforge.event.TickEvent.ServerTickEvent
17 | import net.minecraftforge.event.level.ChunkEvent
18 | import net.minecraftforge.event.server.ServerStartedEvent
19 | import net.minecraftforge.event.server.ServerStoppedEvent
20 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
21 | import net.minecraftforge.registries.NewRegistryEvent
22 | import net.minecraftforge.registries.RegisterEvent
23 | import net.minecraftforge.registries.RegistryBuilder
24 |
25 | fun onCommonSetup(event: FMLCommonSetupEvent) {
26 | registrate.getAll(Registries.BLOCK).forEach {
27 | val block = it.get()
28 | if (block is LateInitSupported) {
29 | block.lateInit()
30 | }
31 | }
32 | KubejsI18nSupport.init()
33 | }
34 |
35 | fun onAddRegistries(event: NewRegistryEvent) {
36 | event.create(
37 | RegistryBuilder>().setName(KeyImageProviderRegistryKey.location())
38 | ) { _KeyImageProviderRegistry = { it } }
39 | }
40 |
41 | fun onChunkUnloaded(event: ChunkEvent.Unload) {
42 | if (event.level is ServerLevel) {
43 | if (event.chunk is LevelChunk) {
44 | (event.chunk as LevelChunk).blockEntities.forEach { (_, blockEntity) ->
45 | if (blockEntity is WebTerminalBlockEntity) {
46 | AENetworkSupport.remove(blockEntity)
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
53 | fun onServerTickPost(event: ServerTickEvent) {
54 | if (event.phase != TickEvent.Phase.END) return
55 | AENetworkSupport.tick()
56 | }
57 |
58 | fun onRegister(event: RegisterEvent) {
59 | if (event.registryKey == KeyImageProviderRegistryKey) {
60 | KeyImageProviderLoader.providers.forEach { t, u ->
61 | event.register(
62 | KeyImageProviderRegistryKey,
63 | t,
64 | u
65 | )
66 | }
67 | }
68 | }
69 |
70 | fun onServerStart(event: ServerStartedEvent) {
71 | HttpServerLifecycleSupport.launch(AppWebTerminal.config.httpPort)
72 | }
73 |
74 | fun onServerStop(event: ServerStoppedEvent) {
75 | HttpServerLifecycleSupport.stop()
76 | AENetworkSupport.reset()
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Items.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import appeng.api.parts.PartModels
4 | import appeng.items.parts.PartItem
5 | import appeng.items.parts.PartModelsHelper
6 | import com.tterrag.registrate.providers.RegistrateRecipeProvider
7 | import icu.takeneko.appwebterminal.AppWebTerminal
8 | import icu.takeneko.appwebterminal.block.part.WebTerminalPart
9 | import icu.takeneko.appwebterminal.registrate
10 | import net.minecraft.data.recipes.RecipeCategory
11 | import net.minecraft.data.recipes.ShapelessRecipeBuilder
12 |
13 | val MEWebTerminalPartItem = registrate
14 | .item>("cable_web_terminal") {
15 | PartItem(
16 | it,
17 | WebTerminalPart::class.java
18 | ) { item -> WebTerminalPart(item) }
19 | }.lang("ME Web Terminal")
20 | .model { _, _ -> }
21 | .recipe { dataGenContext, registrateRecipeProvider ->
22 | ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, dataGenContext.get())
23 | .requires(MEWebTerminal)
24 | .unlockedBy("has_item", RegistrateRecipeProvider.has(MEWebTerminal))
25 | .save(registrateRecipeProvider, AppWebTerminal.location("cable_web_terminal_from_block"))
26 | }.register()
27 |
28 |
29 | fun registerItems() {
30 | PartModels.registerModels(PartModelsHelper.createModels(WebTerminalPart::class.java))
31 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/KeyImageProviders.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package icu.takeneko.appwebterminal.all
4 |
5 | import icu.takeneko.appwebterminal.AppWebTerminal
6 | import icu.takeneko.appwebterminal.api.KeyImageProviderLoader
7 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
8 | import icu.takeneko.appwebterminal.client.rendering.providers.AEFluidKeyImageProvider
9 | import icu.takeneko.appwebterminal.client.rendering.providers.AEItemKeyImageProvider
10 | import net.minecraft.resources.ResourceLocation
11 | import net.minecraftforge.eventbus.api.IEventBus
12 | import net.minecraftforge.registries.DeferredRegister
13 | import net.minecraftforge.registries.RegistryObject
14 |
15 | private val DR = DeferredRegister.create(KeyImageProviderRegistryKey, AppWebTerminal.MOD_ID)
16 |
17 | val itemKeyImageProvider: RegistryObject = DR.register("item") {
18 | AEItemKeyImageProvider
19 | }
20 |
21 | val fluidKeyImageProvider: RegistryObject = DR.register("fluid") {
22 | AEFluidKeyImageProvider
23 | }
24 |
25 | val extensionProviders = mutableMapOf>>()
26 |
27 | fun registerKeyImageProviders(modBus: IEventBus) {
28 | DR.register(modBus)
29 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Networking.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import icu.takeneko.appwebterminal.AppWebTerminal
4 | import icu.takeneko.appwebterminal.networking.OpenWebTerminalScreenPacket
5 | import icu.takeneko.appwebterminal.networking.UpdateWebTerminalNamePacket
6 | import net.minecraftforge.network.NetworkRegistry
7 | import net.minecraftforge.network.simple.SimpleChannel
8 |
9 | private const val version = "1"
10 |
11 | val networkingChannel: SimpleChannel = NetworkRegistry.newSimpleChannel(
12 | AppWebTerminal.location("networking"),
13 | { version },
14 | { it == version },
15 | { it == version }
16 | )
17 |
18 | private var id = 0
19 |
20 | fun registerNetworking() {
21 | networkingChannel.registerMessage(
22 | id++,
23 | OpenWebTerminalScreenPacket::class.java,
24 | OpenWebTerminalScreenPacket::encode,
25 | ::OpenWebTerminalScreenPacket,
26 | OpenWebTerminalScreenPacket::accept
27 | )
28 |
29 | networkingChannel.registerMessage(
30 | id++,
31 | UpdateWebTerminalNamePacket::class.java,
32 | UpdateWebTerminalNamePacket::encode,
33 | ::UpdateWebTerminalNamePacket,
34 | UpdateWebTerminalNamePacket::accept
35 | )
36 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/all/Registries.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.all
2 |
3 | import icu.takeneko.appwebterminal.AppWebTerminal
4 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
5 | import net.minecraft.core.Registry
6 | import net.minecraft.resources.ResourceKey
7 | import net.minecraftforge.registries.IForgeRegistry
8 |
9 | val KeyImageProviderRegistryKey: ResourceKey>> =
10 | ResourceKey.createRegistryKey>(AppWebTerminal.location("key_image_providers"))
11 |
12 | @Suppress("ObjectPropertyName")
13 | internal var _KeyImageProviderRegistry: (() -> IForgeRegistry>)? = null
14 |
15 | val KeyImageProviderRegistry: IForgeRegistry>
16 | get() = _KeyImageProviderRegistry!!()
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/api/KeyImageProviderLoader.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.api
2 |
3 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
4 | import net.minecraft.resources.ResourceLocation
5 | import net.minecraftforge.fml.loading.LoadingModList
6 | import org.slf4j.LoggerFactory
7 | import java.lang.annotation.ElementType
8 |
9 | object KeyImageProviderLoader {
10 | val providers = mutableMapOf AEKeyImageProvider<*>>()
11 | private val ANNOTATION_NAME = "L" + ImageProvider::class.java.name.replace(".", "/") + ";"
12 | private val logger = LoggerFactory.getLogger("KeyImageProviderLoader")
13 |
14 | internal fun compileContents() {
15 | for (modFile in LoadingModList.get().modFiles) {
16 | val scanData = modFile.file.getScanResult()
17 | for (annotation in scanData.annotations) {
18 | if (annotation.annotationType().descriptor == ANNOTATION_NAME
19 | && annotation.targetType() === ElementType.TYPE
20 | ) {
21 | val id = annotation.annotationData()["value"] as String
22 | val modid = annotation.annotationData()["modid"] as String
23 | logger.info("Considering AEKeyImageProvider {} for {}", annotation.memberName(), modid)
24 | if (LoadingModList.get().mods.any { it.modId == modid }) {
25 | this.providers[ResourceLocation(id)] = {
26 | Class.forName(annotation.memberName).run {
27 | this.getConstructor().apply { trySetAccessible() }
28 | }.newInstance() as AEKeyImageProvider<*>
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/api/crafting/CraftingCpuContainer.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.api.crafting
2 |
3 | import appeng.api.config.CpuSelectionMode
4 | import appeng.api.networking.crafting.ICraftingCPU
5 | import appeng.api.stacks.AEKey
6 | import appeng.api.stacks.KeyCounter
7 | import appeng.menu.me.common.IncrementalUpdateHelper
8 | import appeng.menu.me.crafting.CraftingStatus
9 | import java.util.function.Consumer
10 |
11 | interface CraftingCpuContainer {
12 |
13 | fun unwrap(): ICraftingCPU
14 |
15 | fun selectionMode(): CpuSelectionMode
16 |
17 | fun cantStoreItems(): Boolean
18 |
19 | fun createCraftingStatus(helper: IncrementalUpdateHelper): CraftingStatus
20 |
21 | fun removeUpdateListener(cons: Consumer)
22 |
23 | fun addUpdateListener(cons: Consumer)
24 |
25 | fun getAllItems(): KeyCounter
26 |
27 | @FunctionalInterface
28 | interface Constructor where T : ICraftingCPU {
29 | fun create(cpu: T): CraftingCpuContainer
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/api/crafting/CraftingCpuContainerHelper.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.api.crafting
2 |
3 | import appeng.api.networking.crafting.ICraftingCPU
4 |
5 | object CraftingCpuContainerHelper {
6 | private val constructors =
7 | mutableMapOf, CraftingCpuContainer.Constructor>()
8 |
9 | @Suppress("UNCHECKED_CAST")
10 | fun registerConstructor(
11 | clazz: Class,
12 | constructor: CraftingCpuContainer.Constructor
13 | ) where T : ICraftingCPU {
14 | constructors.put(clazz, constructor as CraftingCpuContainer.Constructor)
15 | }
16 |
17 | fun create(
18 | instance: ICraftingCPU
19 | ): CraftingCpuContainer? {
20 | val clazz = instance.javaClass as Class
21 | return constructors[clazz]?.create(instance)
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/api/crafting/impl/AECraftingCpuContainerImpl.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.api.crafting.impl
2 |
3 | import appeng.api.config.CpuSelectionMode
4 | import appeng.api.networking.crafting.ICraftingCPU
5 | import appeng.api.stacks.AEKey
6 | import appeng.api.stacks.KeyCounter
7 | import appeng.me.cluster.implementations.CraftingCPUCluster
8 | import appeng.menu.me.common.IncrementalUpdateHelper
9 | import appeng.menu.me.crafting.CraftingStatus
10 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainer
11 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainerHelper
12 | import java.util.function.Consumer
13 |
14 | class AECraftingCpuContainerImpl(private val cluster: CraftingCPUCluster) : CraftingCpuContainer {
15 |
16 | override fun unwrap(): ICraftingCPU = cluster
17 |
18 | override fun selectionMode(): CpuSelectionMode = cluster.selectionMode
19 |
20 | override fun cantStoreItems(): Boolean = cluster.craftingLogic.isCantStoreItems
21 |
22 | override fun createCraftingStatus(helper: IncrementalUpdateHelper): CraftingStatus {
23 | return CraftingStatus.create(helper, cluster.craftingLogic)
24 | }
25 |
26 | override fun removeUpdateListener(cons: Consumer) {
27 | cluster.craftingLogic.removeListener(cons)
28 | }
29 |
30 | override fun addUpdateListener(cons: Consumer) {
31 | cluster.craftingLogic.addListener(cons)
32 | }
33 |
34 | override fun getAllItems(): KeyCounter {
35 | val counter = KeyCounter()
36 | cluster.craftingLogic.getAllItems(counter)
37 | return counter
38 | }
39 |
40 | companion object {
41 | fun register() =
42 | CraftingCpuContainerHelper.registerConstructor(
43 | CraftingCPUCluster::class.java,
44 | object : CraftingCpuContainer.Constructor {
45 | override fun create(cpu: CraftingCPUCluster) = AECraftingCpuContainerImpl(cpu)
46 | }
47 | )
48 | }
49 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/block/LateInitSupported.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.block
2 |
3 | interface LateInitSupported {
4 | fun lateInit()
5 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/block/WebTerminalBlock.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.block
2 |
3 | import appeng.api.orientation.IOrientationStrategy
4 | import appeng.api.orientation.OrientationStrategies
5 | import appeng.block.AEBaseEntityBlock
6 | import icu.takeneko.appwebterminal.all.MEWebTerminalBlockEntity
7 | import icu.takeneko.appwebterminal.all.networkingChannel
8 | import icu.takeneko.appwebterminal.block.entity.WebTerminalBlockEntity
9 | import icu.takeneko.appwebterminal.networking.OpenWebTerminalScreenPacket
10 | import net.minecraft.core.BlockPos
11 | import net.minecraft.core.Direction
12 | import net.minecraft.server.level.ServerPlayer
13 | import net.minecraft.world.InteractionHand
14 | import net.minecraft.world.InteractionResult
15 | import net.minecraft.world.entity.player.Player
16 | import net.minecraft.world.item.ItemStack
17 | import net.minecraft.world.level.Level
18 | import net.minecraft.world.level.block.Block
19 | import net.minecraft.world.level.block.state.BlockState
20 | import net.minecraft.world.level.block.state.StateDefinition
21 | import net.minecraft.world.level.block.state.properties.BlockStateProperties
22 | import net.minecraft.world.level.block.state.properties.BooleanProperty
23 | import net.minecraft.world.level.block.state.properties.DirectionProperty
24 | import net.minecraft.world.phys.BlockHitResult
25 | import net.minecraftforge.network.PacketDistributor
26 |
27 | class WebTerminalBlock(properties: Properties) : AEBaseEntityBlock(properties),
28 | LateInitSupported {
29 | init {
30 | registerDefaultState(stateDefinition.any().setValue(ONLINE, false).setValue(FACING, Direction.UP))
31 | }
32 |
33 | override fun lateInit() {
34 | setBlockEntity(
35 | WebTerminalBlockEntity::class.java,
36 | MEWebTerminalBlockEntity.get(),
37 | null,
38 | null
39 | )
40 | }
41 |
42 | override fun getOrientationStrategy(): IOrientationStrategy {
43 | return OrientationStrategies.facing()
44 | }
45 |
46 | override fun onActivated(
47 | level: Level,
48 | pos: BlockPos,
49 | player: Player,
50 | hand: InteractionHand,
51 | heldItem: ItemStack?,
52 | hit: BlockHitResult
53 | ): InteractionResult {
54 | if (level.isClientSide) {
55 | return InteractionResult.SUCCESS
56 | }
57 | val be = level.getBlockEntity(pos) as WebTerminalBlockEntity
58 | networkingChannel.send(
59 | PacketDistributor.PLAYER.with { player as ServerPlayer },
60 | OpenWebTerminalScreenPacket(be.displayName, be.getId(), pos, be.password, be.mainNode.isOnline)
61 | )
62 | return InteractionResult.SUCCESS
63 | }
64 |
65 | override fun createBlockStateDefinition(builder: StateDefinition.Builder) {
66 | builder.add(ONLINE, FACING)
67 | }
68 |
69 | companion object {
70 | val ONLINE: BooleanProperty = BooleanProperty.create("online")
71 | val FACING: DirectionProperty = BlockStateProperties.FACING
72 | }
73 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/block/entity/WebTerminalBlockEntity.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.block.entity
2 |
3 | import appeng.api.networking.GridFlags
4 | import appeng.api.networking.IGrid
5 | import appeng.api.networking.IGridNodeListener
6 | import appeng.api.orientation.BlockOrientation
7 | import appeng.blockentity.grid.AENetworkBlockEntity
8 | import icu.takeneko.appwebterminal.all.MEWebTerminal
9 | import icu.takeneko.appwebterminal.block.WebTerminalBlock
10 | import icu.takeneko.appwebterminal.support.AENetworkAccess
11 | import icu.takeneko.appwebterminal.support.AENetworkSupport
12 | import icu.takeneko.appwebterminal.util.get
13 | import io.ktor.util.*
14 | import net.minecraft.core.BlockPos
15 | import net.minecraft.core.Direction
16 | import net.minecraft.nbt.CompoundTag
17 | import net.minecraft.server.level.ServerLevel
18 | import net.minecraft.world.entity.player.Player
19 | import net.minecraft.world.level.Level
20 | import net.minecraft.world.level.block.Block
21 | import net.minecraft.world.level.block.entity.BlockEntityType
22 | import net.minecraft.world.level.block.state.BlockState
23 | import java.util.UUID
24 |
25 | class WebTerminalBlockEntity(
26 | blockEntityType: BlockEntityType<*>,
27 | blockPos: BlockPos,
28 | blockState: BlockState
29 | ) : AENetworkBlockEntity(blockEntityType, blockPos, blockState), AENetworkAccess {
30 |
31 | private var id: UUID = UUID.randomUUID()
32 | var displayName: String = "ME Web Terminal"
33 | private set
34 | var password: String = "AppliedWebTerminal"
35 | private set
36 |
37 | private var nonce = generateNonce()
38 | private var registered = false
39 |
40 | init {
41 | this.mainNode.setExposedOnSides(Direction.entries.toMutableSet() - blockState[WebTerminalBlock.FACING])
42 | this.mainNode.setVisualRepresentation(MEWebTerminal.asStack())
43 | this.mainNode.setIdlePowerUsage(3.0)
44 | this.mainNode.setFlags(GridFlags.REQUIRE_CHANNEL)
45 | }
46 |
47 | override fun getGridConnectableSides(orientation: BlockOrientation): Set {
48 | return Direction.entries.toMutableSet() - (level?.getBlockState(worldPosition)
49 | ?: blockState)[WebTerminalBlock.FACING]
50 | }
51 |
52 | private fun updateExposedSides() {
53 | this.mainNode.setExposedOnSides(Direction.entries.toMutableSet() - level!!.getBlockState(worldPosition)[WebTerminalBlock.FACING])
54 | }
55 |
56 | override fun saveAdditional(data: CompoundTag) {
57 | super.saveAdditional(data)
58 | data.putString("Name", displayName)
59 | data.putUUID("UUID", id)
60 | data.putString("Password", password)
61 | }
62 |
63 | override fun loadTag(data: CompoundTag) {
64 | super.loadTag(data)
65 | this.displayName = if (data.contains("Name")) data.getString("Name") else "ME Web Terminal"
66 | this.id = if (data.hasUUID("UUID")) data.getUUID("UUID") else UUID.randomUUID()
67 | this.password = if (data.contains("Password")) data.getString("Password") else "AppliedWebTerminal"
68 | }
69 |
70 | override fun onReady() {
71 | super.onReady()
72 | updateState()
73 | }
74 |
75 | override fun onMainNodeStateChanged(reason: IGridNodeListener.State) {
76 | updateState()
77 | }
78 |
79 | private fun updateState() {
80 | level!!.setBlock(
81 | this.worldPosition,
82 | level!!.getBlockState(blockPos).setValue(WebTerminalBlock.ONLINE, mainNode.isOnline),
83 | Block.UPDATE_CLIENTS
84 | )
85 | if (!registered && mainNode.isOnline) {
86 | AENetworkSupport.register(this)
87 | registered = true
88 | }
89 | if (registered && !mainNode.isOnline) {
90 | AENetworkSupport.remove(this)
91 | registered = false
92 | }
93 |
94 | }
95 |
96 | override fun setOwner(owner: Player?) {
97 | super.setOwner(owner)
98 | val ownerName = owner?.gameProfile?.name
99 | if ( ownerName != null) {
100 | this.displayName = "${ownerName}'s Web Terminal"
101 | markDirty()
102 | }
103 | }
104 |
105 | override fun onOrientationChanged(orientation: BlockOrientation) {
106 | super.onOrientationChanged(orientation)
107 | updateExposedSides()
108 | }
109 |
110 | override fun setRemoved() {
111 | super.setRemoved()
112 | if (this.level is ServerLevel) {
113 | AENetworkSupport.remove(this)
114 | }
115 | }
116 |
117 | override fun markDirty() {
118 | setChanged()
119 | }
120 |
121 | override fun getGrid(): IGrid? {
122 | return this.mainNode.grid
123 | }
124 |
125 | override fun auth(password: String): Boolean {
126 | return password == this.password
127 | }
128 |
129 | override fun update(displayName: String, password: String): Boolean {
130 | this.displayName = displayName
131 | val oldPassword = this.password
132 | this.password = password
133 | return if (oldPassword != password) {
134 | AENetworkSupport.requestSessionReset(this)
135 | this.nonce = generateNonce()
136 | true
137 | } else {
138 | false
139 | }
140 | }
141 |
142 | override fun validateNonce(nonce: String): Boolean {
143 | return nonce == this.nonce
144 | }
145 |
146 | override fun getNonce(): String {
147 | return nonce
148 | }
149 |
150 | override fun getTerminalName(): String {
151 | return displayName
152 | }
153 |
154 | override fun level(): Level? {
155 | return getLevel()
156 | }
157 |
158 | override fun getId(): UUID = id
159 |
160 | override fun worldPosition(): BlockPos = worldPosition
161 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/ClientEvents.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client
2 |
3 | import com.mojang.blaze3d.vertex.DefaultVertexFormat
4 | import icu.takeneko.appwebterminal.AppWebTerminal
5 | import icu.takeneko.appwebterminal.client.all.BlurPostProcessInstance
6 | import icu.takeneko.appwebterminal.client.all.BlurShader
7 | import icu.takeneko.appwebterminal.client.all.TexturedRoundRect
8 | import icu.takeneko.appwebterminal.client.all.blurShaderLoaded
9 | import icu.takeneko.appwebterminal.client.all.createPostProcess
10 | import icu.takeneko.appwebterminal.client.all.roundRectShaderLoaded
11 | import icu.takeneko.appwebterminal.client.gui.WebTerminalScreen
12 | import icu.takeneko.appwebterminal.client.rendering.AEKeyRenderer
13 | import net.minecraft.client.Minecraft
14 | import net.minecraft.client.renderer.ShaderInstance
15 | import net.minecraftforge.client.event.RegisterShadersEvent
16 | import net.minecraftforge.client.event.RenderLevelStageEvent
17 |
18 |
19 | fun onLoadShaders(event: RegisterShadersEvent) {
20 | print("11111")
21 | val res = event.resourceProvider
22 | event.registerShader(
23 | ShaderInstance(
24 | res,
25 | AppWebTerminal.location("blur"),
26 | DefaultVertexFormat.POSITION
27 | )
28 | ) {
29 | BlurShader = it
30 | blurShaderLoaded = true
31 | createPostProcess()
32 | }
33 |
34 | event.registerShader(
35 | ShaderInstance(
36 | res,
37 | AppWebTerminal.location("rendertype_textured_round_rect"),
38 | DefaultVertexFormat.POSITION
39 | )
40 | ) {
41 | TexturedRoundRect = it
42 | roundRectShaderLoaded = true
43 | createPostProcess()
44 | }
45 | }
46 |
47 | fun onRenderLevelPost(event: RenderLevelStageEvent) {
48 | if (event.stage == RenderLevelStageEvent.Stage.AFTER_LEVEL) {
49 | if (Minecraft.getInstance().screen is WebTerminalScreen) {
50 | val mainRenderTarget = Minecraft.getInstance().mainRenderTarget
51 | BlurPostProcessInstance?.apply(mainRenderTarget.colorTextureId)
52 | mainRenderTarget.bindWrite(true)
53 | }
54 | AEKeyRenderer.instance?.next()
55 | }
56 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/all/Shaders.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.all
2 |
3 | import icu.takeneko.appwebterminal.client.rendering.foundation.BlurPostProcess
4 | import icu.takeneko.appwebterminal.client.rendering.foundation.SamplingBlurPostProcess
5 | import net.minecraft.client.Minecraft
6 | import net.minecraft.client.renderer.ShaderInstance
7 |
8 | internal lateinit var TexturedRoundRect: ShaderInstance
9 | internal lateinit var BlurShader: ShaderInstance
10 |
11 | internal var roundRectShaderLoaded = false
12 | internal var blurShaderLoaded = false
13 |
14 | internal var BlurPostProcessInstance: BlurPostProcess? = null
15 |
16 | internal fun createPostProcess() {
17 | if (roundRectShaderLoaded && blurShaderLoaded) {
18 | val window = Minecraft.getInstance().window
19 | BlurPostProcessInstance = BlurPostProcess(window.width, window.height)
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/AEKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering
2 |
3 | import appeng.api.client.AEKeyRenderHandler
4 | import appeng.api.client.AEKeyRendering
5 | import appeng.api.stacks.AEKey
6 | import appeng.api.stacks.AEKeyType
7 | import com.mojang.blaze3d.vertex.PoseStack
8 | import net.minecraft.client.Minecraft
9 | import net.minecraft.client.renderer.LightTexture
10 | import net.minecraft.client.renderer.MultiBufferSource.BufferSource
11 | import kotlin.math.min
12 |
13 | interface AEKeyImageProvider {
14 | val keyType: AEKeyType
15 | fun renderImage(
16 | aeKey: T,
17 | poseStack: PoseStack,
18 | bufferSource: BufferSource,
19 | canvasSizeX: Int,
20 | canvasSizeY: Int
21 | ) {
22 | @Suppress("UNCHECKED_CAST")
23 | val renderer = AEKeyRendering.getOrThrow(keyType) as AEKeyRenderHandler
24 | poseStack.translate(
25 | canvasSizeX / 2f,
26 | canvasSizeY / 2f,
27 | 500f
28 | )
29 | poseStack.scale(1f, -1f, 10f)
30 | renderer.drawOnBlockFace(
31 | poseStack,
32 | bufferSource,
33 | aeKey,
34 | (min(canvasSizeX, canvasSizeY) * 0.8).toFloat(),
35 | LightTexture.FULL_BLOCK,
36 | Minecraft.getInstance().level
37 | )
38 | }
39 |
40 | fun getAllEntries(): Iterable
41 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/AEKeyRenderer.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering
2 |
3 | import appeng.api.stacks.AEKey
4 | import com.mojang.blaze3d.platform.Lighting
5 | import com.mojang.blaze3d.platform.NativeImage
6 | import com.mojang.blaze3d.systems.RenderSystem
7 | import com.mojang.blaze3d.vertex.PoseStack
8 | import icu.takeneko.appwebterminal.all.KeyImageProviderRegistry
9 | import icu.takeneko.appwebterminal.client.rendering.foundation.FrameBuffer
10 | import icu.takeneko.appwebterminal.client.rendering.foundation.FullyBufferedBufferSource
11 | import net.minecraft.client.Minecraft
12 | import org.joml.Matrix4f
13 | import org.slf4j.LoggerFactory
14 | import java.nio.file.Path
15 | import java.util.ArrayDeque
16 | import kotlin.io.path.createFile
17 | import kotlin.io.path.createParentDirectories
18 | import kotlin.io.path.deleteIfExists
19 | import kotlin.io.path.div
20 |
21 | class AEKeyRenderer(
22 | private val sizeX: Int,
23 | private val sizeY: Int
24 | ) {
25 | private val frameBuffer = FrameBuffer(
26 | sizeX,
27 | sizeY
28 | )
29 | private val nativeImage = NativeImage(
30 | sizeX,
31 | sizeY,
32 | false
33 | )
34 |
35 | private val projectionMatrix = Matrix4f().setOrtho(
36 | 0f,
37 | sizeX.toFloat(),
38 | sizeY.toFloat(),
39 | 0f,
40 | -1000f,
41 | 1000f
42 | )
43 |
44 | private val logger = LoggerFactory.getLogger("AEKeyRenderer")
45 | val renderTasks = ArrayDeque<() -> Unit>()
46 | var taskPullLimit = 1
47 | private var lastNotify: Long = 0
48 |
49 | fun submitRenderTasks(basePath: Path, progressListener: RenderProgressListener) {
50 | rendering = true
51 | KeyImageProviderRegistry.values.toList().forEach { prov ->
52 | val entries = prov.getAllEntries().toList()
53 | renderTasks += {
54 | progressListener.notifyTotalCount(entries.size)
55 | }
56 | entries.forEachIndexed { index, it ->
57 | renderTasks += {
58 | val id = it.id
59 | if (System.currentTimeMillis() - lastNotify > 200) {
60 | progressListener.notifyProgress(index, it)
61 | lastNotify = System.currentTimeMillis()
62 | }
63 | @Suppress("UNCHECKED_CAST")
64 | renderSingle(
65 | it,
66 | prov as AEKeyImageProvider,
67 | basePath
68 | / it.type.id.toString().replace(":", "_")
69 | / "$id.png".replace(":", "_")
70 | )
71 | }
72 | }
73 | }
74 | renderTasks += {
75 | progressListener.notifyCompleted()
76 | rendering = false
77 | }
78 | }
79 |
80 | fun next() {
81 | var limit = taskPullLimit
82 | while (renderTasks.isNotEmpty() && limit >= 0){
83 | renderTasks.pop()()
84 | limit--
85 | }
86 | }
87 |
88 | fun renderSingle(key: T, provider: AEKeyImageProvider, path: Path) {
89 | val bufferSource = FullyBufferedBufferSource()
90 | val poseStack = PoseStack()
91 | poseStack.pushPose()
92 | frameBuffer.clear()
93 | Lighting.setupForEntityInInventory()
94 | RenderSystem.enableCull()
95 | RenderSystem.setShaderColor(0.99f, 0.99f, 0.99f, 1f)
96 | frameBuffer.bindWrite(true)
97 | try {
98 | provider.renderImage(
99 | key,
100 | poseStack,
101 | bufferSource,
102 | sizeX,
103 | sizeY
104 | )
105 | } catch (e: Exception) {
106 | logger.error("Error while rendering ${key.type.id}/${key.id}", e)
107 | }
108 | val uploadResult = bufferSource.upload()
109 | uploadResult.forEach { t, u ->
110 | t.setupRenderState()
111 | frameBuffer.bindWrite(true)
112 | u.drawWithShader(
113 | Matrix4f(),
114 | projectionMatrix,
115 | RenderSystem.getShader()
116 | )
117 | t.clearRenderState()
118 | }
119 | frameBuffer.unbindWrite()
120 | Minecraft.getInstance().mainRenderTarget.bindWrite(true)
121 | frameBuffer.bindRead()
122 | nativeImage.downloadTexture(0, true)
123 | nativeImage.flipY()
124 | nativeImage.applyToAllPixels {
125 | if (it == -16777216) {
126 | return@applyToAllPixels 0
127 | }
128 | it
129 | }
130 | path.apply {
131 | createParentDirectories()
132 | deleteIfExists()
133 | createFile()
134 | nativeImage.writeToFile(this)
135 | }
136 | }
137 |
138 | fun dispose() {
139 | frameBuffer.dispose()
140 | nativeImage.close()
141 | }
142 |
143 | companion object {
144 | var rendering = false
145 | private set
146 | var instance: AEKeyRenderer? = null
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/JProgressWindow.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering
2 |
3 | import appeng.api.stacks.AEKey
4 | import javax.swing.BoxLayout
5 | import javax.swing.JFrame
6 | import javax.swing.JLabel
7 | import javax.swing.JPanel
8 | import javax.swing.JProgressBar
9 |
10 | class JProgressWindow(
11 | val title: String
12 | ) : RenderProgressListener {
13 | private val window: JFrame
14 | private val progressText: JLabel
15 | private val progressBar: JProgressBar
16 | private var totalCount:Int = 0
17 |
18 | init {
19 | window = JFrame(title)
20 | window.setSize(450, 200)
21 | val pane = JPanel()
22 | progressText = JLabel()
23 | progressBar = JProgressBar()
24 | progressText.text = "Progress: ?"
25 | pane.layout = BoxLayout(pane, BoxLayout.Y_AXIS)
26 | pane.add(progressText)
27 | pane.add(progressBar)
28 | window.add(pane)
29 | window.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE
30 | progressBar.isIndeterminate = true
31 | progressBar.isStringPainted = true
32 | }
33 |
34 | override fun notifyTotalCount(size: Int) {
35 | progressBar.isIndeterminate = false
36 | totalCount = size
37 | progressText.text = "Progress: $totalCount/?"
38 | progressBar.minimum = 0
39 | progressBar.maximum = totalCount
40 | progressBar.value = 0
41 | }
42 |
43 | override fun notifyProgress(current: Int, what: AEKey) {
44 | progressText.text = "Progress: $totalCount/$current Current: ${what.id}"
45 | progressBar.value = current
46 | }
47 |
48 | override fun notifyCompleted() {
49 | dismiss()
50 | }
51 |
52 | fun show() {
53 | window.isVisible = true
54 | }
55 |
56 | fun dismiss() {
57 | window.isVisible = false
58 | window.dispose()
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/RenderProgressListener.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering
2 |
3 | import appeng.api.stacks.AEKey
4 |
5 | interface RenderProgressListener {
6 | fun notifyTotalCount(size: Int)
7 |
8 | fun notifyProgress(current: Int, what: AEKey)
9 |
10 | fun notifyCompleted()
11 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/foundation/BlurPostProcess.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.foundation
2 |
3 | import com.mojang.blaze3d.systems.RenderSystem
4 | import icu.takeneko.appwebterminal.client.all.BlurShader
5 | import net.minecraft.client.Minecraft
6 | import net.minecraft.client.renderer.FogRenderer
7 | import net.minecraft.client.renderer.ShaderInstance
8 |
9 | class BlurPostProcess(xSize: Int, ySize: Int) : PostProcess(xSize, ySize) {
10 |
11 | private val swap1 = FrameBuffer(xSize, ySize)
12 | private val swap2 = FrameBuffer(xSize, ySize)
13 | val output = FrameBuffer(xSize, ySize)
14 | private val blurShader = BlurShader
15 |
16 |
17 | override fun resize(xSize: Int, ySize: Int) {
18 | super.resize(xSize, ySize)
19 | swap1.resize(xSize, ySize)
20 | swap2.resize(xSize, ySize)
21 | output.resize(xSize, ySize)
22 | }
23 |
24 | override fun apply(inputTexture: Int) {
25 | FogRenderer.levelFogColor()
26 | val background = RenderSystem.getShaderFogColor()
27 | swap1.setClearColor(background[0], background[1], background[2], background[3])
28 | swap2.setClearColor(background[0], background[1], background[2], background[3])
29 | output.setClearColor(background[0], background[1], background[2], background[3])
30 | swap1.clear()
31 | swap2.clear()
32 | output.clear()
33 | processOnce(
34 | blurShader,
35 | inputTexture,
36 | swap1,
37 | ) {
38 | applyCommonUniforms(this)
39 | safeGetUniform("BlurDir").set(0f, 1f)
40 | }
41 | processOnce(
42 | blurShader,
43 | swap1.colorTextureId,
44 | swap2,
45 | ) {
46 | applyCommonUniforms(this)
47 | safeGetUniform("BlurDir").set(1f, 0f)
48 | }
49 | processOnce(
50 | blurShader,
51 | swap2.colorTextureId,
52 | swap1,
53 | ) {
54 | applyCommonUniforms(this)
55 | safeGetUniform("BlurDir").set(0f, 1f)
56 | }
57 | processOnce(
58 | blurShader,
59 | swap1.colorTextureId,
60 | swap2,
61 | ) {
62 | applyCommonUniforms(this)
63 | safeGetUniform("BlurDir").set(1f, 0f)
64 | }
65 | output.copyColorsFrom(swap2.framebufferId)
66 | }
67 |
68 | fun applyCommonUniforms(instance: ShaderInstance) {
69 | instance.safeGetUniform("ProjMat").set(projectionMatrix)
70 | instance.safeGetUniform("InSize").set(xSize.toFloat(), ySize.toFloat())
71 | instance.safeGetUniform("OutSize").set(xSize.toFloat(), ySize.toFloat())
72 | }
73 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/foundation/FullyBufferedBufferSource.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.foundation
2 |
3 | import com.mojang.blaze3d.vertex.BufferBuilder
4 | import com.mojang.blaze3d.vertex.VertexBuffer
5 | import com.mojang.blaze3d.vertex.VertexConsumer
6 | import it.unimi.dsi.fastutil.objects.Reference2IntMap
7 | import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
8 | import net.minecraft.MethodsReturnNonnullByDefault
9 | import net.minecraft.client.renderer.MultiBufferSource
10 | import net.minecraft.client.renderer.RenderType
11 | import org.lwjgl.system.MemoryUtil
12 | import org.lwjgl.system.MemoryUtil.MemoryAllocator
13 | import java.util.function.Function
14 | import javax.annotation.ParametersAreNonnullByDefault
15 |
16 | @ParametersAreNonnullByDefault
17 | @MethodsReturnNonnullByDefault
18 | class FullyBufferedBufferSource : MultiBufferSource.BufferSource(null, null) {
19 | private val bufferBuilders: MutableMap = mutableMapOf()
20 | val indexCountMap: Reference2IntMap = Reference2IntOpenHashMap()
21 |
22 | override fun getBuffer(renderType: RenderType): VertexConsumer {
23 | return bufferBuilders.computeIfAbsent(
24 | renderType
25 | ) { BufferBuilder(15720).also { it.begin(renderType.mode(), renderType.format()) } }
26 | }
27 |
28 | val isEmpty: Boolean
29 | get() = !bufferBuilders.isEmpty()
30 | && bufferBuilders.values.stream().noneMatch { it.isCurrentBatchEmpty() }
31 |
32 | override fun endBatch(renderType: RenderType) {
33 | }
34 |
35 | override fun endBatch() {
36 | }
37 |
38 | override fun endLastBatch() {
39 | }
40 |
41 | fun upload(): Map {
42 | val result = mutableMapOf()
43 | bufferBuilders.forEach { (key, value) ->
44 | if (value.isCurrentBatchEmpty) return@forEach
45 | val mesh = value.end()
46 | val vertexBuffer = VertexBuffer(VertexBuffer.Usage.STATIC)
47 | vertexBuffer.bind()
48 | vertexBuffer.upload(mesh)
49 | result[key] = vertexBuffer
50 | }
51 | return result
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/foundation/PostProcess.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.foundation
2 |
3 | import com.mojang.blaze3d.shaders.ProgramManager
4 | import com.mojang.blaze3d.systems.RenderSystem
5 | import com.mojang.blaze3d.vertex.BufferBuilder
6 | import com.mojang.blaze3d.vertex.BufferUploader
7 | import com.mojang.blaze3d.vertex.DefaultVertexFormat
8 | import com.mojang.blaze3d.vertex.Tesselator
9 | import com.mojang.blaze3d.vertex.VertexFormat
10 | import io.ktor.websocket.Frame
11 | import net.minecraft.client.renderer.ShaderInstance
12 | import org.joml.Matrix4f
13 | import org.lwjgl.opengl.GL11
14 |
15 | abstract class PostProcess(
16 | protected var xSize: Int,
17 | protected var ySize: Int
18 | ) {
19 |
20 | protected val projectionMatrix: Matrix4f = Matrix4f().setOrtho(
21 | 0f,
22 | xSize.toFloat(),
23 | 0f,
24 | ySize.toFloat(),
25 | 0.1f,
26 | 1000f
27 | )
28 |
29 | protected val mvMat = Matrix4f()
30 |
31 | abstract fun apply(inputTexture: Int)
32 |
33 | open fun resize(xSize: Int, ySize: Int){
34 | this.xSize = xSize
35 | this.ySize = ySize
36 | projectionMatrix.setOrtho(
37 | 0f,
38 | xSize.toFloat(),
39 | 0f,
40 | ySize.toFloat(),
41 | 0.1f,
42 | 1000f
43 | )
44 | }
45 |
46 | protected inline fun processOnce(
47 | shader: ShaderInstance,
48 | inputTexture: Int,
49 | writeFramebuffer: FrameBuffer,
50 | crossinline uniformSetter: ShaderInstance.() -> Unit
51 | ) {
52 | RenderSystem.setShader { shader }
53 | shader.setSampler("DiffuseSampler", inputTexture)
54 | shader.uniformSetter()
55 | RenderSystem.depthFunc(GL11.GL_ALWAYS)
56 | RenderSystem.disableDepthTest()
57 | val bufferBuilder: BufferBuilder = Tesselator.getInstance().builder
58 | bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
59 | bufferBuilder.vertex(0.0, 0.0, 500.0).endVertex()
60 | bufferBuilder.vertex(xSize.toDouble(), 0.0, 500.0).endVertex()
61 | bufferBuilder.vertex(xSize.toDouble(), ySize.toDouble(), 500.0).endVertex()
62 | bufferBuilder.vertex(0.0, ySize.toDouble(), 500.0).endVertex()
63 |
64 | shader.apply()
65 | writeFramebuffer.bindWrite(true)
66 | BufferUploader.draw(bufferBuilder.end())
67 | writeFramebuffer.unbindWrite()
68 | RenderSystem.depthFunc(GL11.GL_LEQUAL)
69 | RenderSystem.enableDepthTest()
70 | RenderSystem.setShader { null }
71 | shader.clear()
72 | }
73 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/foundation/SamplingBlurPostProcess.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.foundation
2 |
3 | import org.lwjgl.opengl.GL30
4 | import org.slf4j.LoggerFactory
5 |
6 | class SamplingBlurPostProcess(
7 | xSize: Int,
8 | ySize: Int,
9 | private val iterations: Int
10 | ) : PostProcess(xSize, ySize) {
11 |
12 | private val logger = LoggerFactory.getLogger("SamplingBlurPostProcess")
13 | private var buffers = createFramebuffers()
14 |
15 | val output: FrameBuffer
16 | get() = buffers[0]
17 |
18 | private fun createFramebuffers() = buildList {
19 | for (i in 0 until iterations) {
20 | val x = xSize shr i
21 | val y = ySize shr i
22 | if (x < 5 || y < 5) {
23 | logger.warn("Iterations too big for current size!")
24 | break
25 | }
26 | add(FrameBuffer(x, y, false))
27 | }
28 | }
29 |
30 | override fun resize(xSize: Int, ySize: Int) {
31 | buffers.forEach { it.dispose() }
32 | this.xSize = xSize
33 | this.ySize = ySize
34 | buffers = createFramebuffers()
35 | }
36 |
37 | private fun blitNamed(
38 | from: Int,
39 | to: Int,
40 | x1: Int,
41 | y1: Int,
42 | x2: Int,
43 | y2: Int
44 | ) {
45 | GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, from)
46 | GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, to)
47 | GL30.glBlitFramebuffer(
48 | 0, 0,
49 | x1, y1,
50 | 0, 0,
51 | x2, y2,
52 | GL30.GL_COLOR_BUFFER_BIT,
53 | GL30.GL_NEAREST
54 | )
55 | }
56 |
57 | override fun apply(inputFramebuffer: Int) {
58 | blitNamed(
59 | inputFramebuffer,
60 | buffers[0].framebufferId,
61 | xSize, ySize,
62 | xSize, ySize
63 | )
64 | for (i in 1 until buffers.size) {
65 | val from = buffers[i - 1]
66 | val to = buffers[i]
67 | blitNamed(
68 | from.framebufferId,
69 | to.framebufferId,
70 | from.xSize, from.ySize,
71 | to.xSize, to.xSize
72 | )
73 | }
74 | for (i in (buffers.size - 1) downTo 1) {
75 | val from = buffers[i]
76 | val to = buffers[i - 1]
77 | blitNamed(
78 | from.framebufferId,
79 | to.framebufferId,
80 | from.xSize, from.ySize,
81 | to.xSize, to.xSize
82 | )
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/providers/AEFluidKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.providers
2 |
3 | import appeng.api.stacks.AEFluidKey
4 | import appeng.api.stacks.AEKeyType
5 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
6 | import net.minecraftforge.registries.ForgeRegistries
7 |
8 | object AEFluidKeyImageProvider : AEKeyImageProvider {
9 | override val keyType: AEKeyType = AEKeyType.fluids()
10 |
11 | override fun getAllEntries(): Iterable {
12 | return ForgeRegistries.FLUIDS.values.map { AEFluidKey.of(it) }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/client/rendering/providers/AEItemKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.client.rendering.providers
2 |
3 | import appeng.api.stacks.AEItemKey
4 | import appeng.api.stacks.AEKeyType
5 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
6 | import net.minecraftforge.registries.ForgeRegistries
7 |
8 | object AEItemKeyImageProvider : AEKeyImageProvider {
9 | override val keyType: AEKeyType = AEKeyType.items()
10 |
11 | override fun getAllEntries(): Iterable = ForgeRegistries.ITEMS.map { AEItemKey.of(it) }
12 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/compat/advancedae/AAECraftingCpuContainerImpl.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.compat.advancedae
2 |
3 | import appeng.api.config.CpuSelectionMode
4 | import appeng.api.networking.crafting.ICraftingCPU
5 | import appeng.api.stacks.AEKey
6 | import appeng.api.stacks.KeyCounter
7 | import appeng.menu.me.common.IncrementalUpdateHelper
8 | import appeng.menu.me.crafting.CraftingStatus
9 | import appeng.menu.me.crafting.CraftingStatusEntry
10 | import com.google.common.collect.ImmutableList
11 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainer
12 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainerHelper
13 | import net.minecraftforge.fml.ModList
14 | import net.minecraftforge.fml.loading.LoadingModList
15 | import net.pedroksl.advanced_ae.common.cluster.AdvCraftingCPU
16 | import net.pedroksl.advanced_ae.common.logic.AdvCraftingCPULogic
17 | import java.util.function.Consumer
18 |
19 | class AAECraftingCpuContainerImpl(private val cpu: AdvCraftingCPU) : CraftingCpuContainer {
20 | override fun unwrap(): ICraftingCPU = cpu
21 |
22 | override fun selectionMode(): CpuSelectionMode = cpu.selectionMode
23 | override fun cantStoreItems(): Boolean = cpu.craftingLogic.isCantStoreItems
24 |
25 | override fun createCraftingStatus(helper: IncrementalUpdateHelper): CraftingStatus {
26 | return createCraftingStatusInternal(helper, cpu.craftingLogic)
27 | }
28 |
29 | @Suppress("DEPRECATION")
30 | private fun createCraftingStatusInternal(
31 | changes: IncrementalUpdateHelper,
32 | logic: AdvCraftingCPULogic
33 | ): CraftingStatus {
34 | val full = changes.isFullUpdate()
35 | val newEntries = ImmutableList.builder()
36 |
37 | for (what in changes) {
38 | val storedCount = logic.getStored(what)
39 | val activeCount = logic.getWaitingFor(what)
40 | val pendingCount = logic.getPendingOutputs(what)
41 | var sentStack = what
42 | if (!full && changes.getSerial(what) != null) {
43 | sentStack = null
44 | }
45 |
46 | val entry = CraftingStatusEntry(
47 | changes.getOrAssignSerial(what),
48 | sentStack,
49 | storedCount,
50 | activeCount,
51 | pendingCount
52 | )
53 | newEntries.add(entry)
54 | if (entry.isDeleted()) {
55 | changes.removeSerial(what)
56 | }
57 | }
58 |
59 | val elapsedTime = logic.getElapsedTimeTracker().elapsedTime
60 | val remainingItems = logic.getElapsedTimeTracker().remainingItemCount
61 | val startItems = logic.getElapsedTimeTracker().startItemCount
62 | return CraftingStatus(
63 | full,
64 | elapsedTime,
65 | remainingItems,
66 | startItems,
67 | newEntries.build()
68 | )
69 | }
70 |
71 | override fun removeUpdateListener(cons: Consumer) {
72 | cpu.craftingLogic.removeListener(cons)
73 | }
74 |
75 | override fun addUpdateListener(cons: Consumer) {
76 | cpu.craftingLogic.addListener(cons)
77 | }
78 |
79 | override fun getAllItems(): KeyCounter {
80 | val keyCounter = KeyCounter()
81 | cpu.craftingLogic.getAllItems(keyCounter)
82 | return keyCounter
83 | }
84 | }
85 |
86 | fun registerAAECompat() {
87 | if (ModList.get().isLoaded("advanced_ae")) {
88 | CraftingCpuContainerHelper.registerConstructor(
89 | AdvCraftingCPU::class.java,
90 | object : CraftingCpuContainer.Constructor{
91 | override fun create(cpu: AdvCraftingCPU): CraftingCpuContainer = AAECraftingCpuContainerImpl(cpu)
92 | }
93 | )
94 | }
95 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/compat/appbot/ManaKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.compat.appbot
2 |
3 | import appbot.ae2.ManaKey
4 | import appbot.ae2.ManaKeyType
5 | import appeng.api.stacks.AEKeyType
6 | import icu.takeneko.appwebterminal.api.ImageProvider
7 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
8 |
9 | @ImageProvider(
10 | value = "botania:mana",
11 | modid = "appbot"
12 | )
13 | class ManaKeyImageProvider : AEKeyImageProvider {
14 | override val keyType: AEKeyType = ManaKeyType.TYPE
15 |
16 | override fun getAllEntries(): Iterable {
17 | return listOf(ManaKey.KEY as ManaKey)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/compat/appflux/FluxKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.compat.appflux
2 |
3 | import com.glodblock.github.appflux.common.me.key.FluxKey
4 | import com.glodblock.github.appflux.common.me.key.type.EnergyType
5 | import com.glodblock.github.appflux.common.me.key.type.FluxKeyType
6 | import icu.takeneko.appwebterminal.api.ImageProvider
7 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
8 |
9 | @ImageProvider(
10 | value = "appflux:flux",
11 | modid = "appflux"
12 | )
13 | class FluxKeyImageProvider : AEKeyImageProvider {
14 | override val keyType: FluxKeyType = FluxKeyType.TYPE
15 |
16 | override fun getAllEntries(): Iterable {
17 | return EnergyType.entries.map { FluxKey.of(it)!! }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/compat/appmek/ChemicalKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.compat.appmek
2 |
3 | import appeng.api.stacks.AEKeyType
4 | import icu.takeneko.appwebterminal.api.ImageProvider
5 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
6 | import me.ramidzkh.mekae2.ae2.MekanismKey
7 | import me.ramidzkh.mekae2.ae2.MekanismKeyType
8 | import mekanism.api.MekanismAPI
9 |
10 | @ImageProvider(
11 | value = "appmek:chemical",
12 | modid = "appmek"
13 | )
14 | class ChemicalKeyImageProvider : AEKeyImageProvider {
15 | override val keyType: AEKeyType = MekanismKeyType.TYPE
16 |
17 | override fun getAllEntries(): Iterable {
18 | return (MekanismAPI.gasRegistry()
19 | + MekanismAPI.infuseTypeRegistry()
20 | + MekanismAPI.pigmentRegistry()
21 | + MekanismAPI.slurryRegistry()).mapNotNull {
22 | MekanismKey.of(it.getStack(1000))
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/compat/arseng/SourceKeyImageProvider.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.compat.arseng
2 |
3 | import appeng.api.stacks.AEKeyType
4 | import gripe._90.arseng.me.key.SourceKey
5 | import gripe._90.arseng.me.key.SourceKeyType
6 | import icu.takeneko.appwebterminal.api.ImageProvider
7 | import icu.takeneko.appwebterminal.client.rendering.AEKeyImageProvider
8 |
9 | @ImageProvider(
10 | value = "arseng:source",
11 | modid = "arseng"
12 | )
13 | class SourceKeyImageProvider : AEKeyImageProvider {
14 | override val keyType: AEKeyType = SourceKeyType.TYPE
15 |
16 | override fun getAllEntries(): Iterable {
17 | return listOf(SourceKey.KEY as SourceKey)
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/data/DataGenerator.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.data
2 |
3 | import com.tterrag.registrate.providers.ProviderType
4 | import com.tterrag.registrate.providers.RegistrateLangProvider
5 | import dev.toma.configuration.config.value.ConfigValue
6 | import dev.toma.configuration.config.value.ObjectValue
7 | import icu.takeneko.appwebterminal.AppWebTerminal
8 | import icu.takeneko.appwebterminal.registrate
9 | import icu.takeneko.appwebterminal.util.toEnglishName
10 | import icu.takeneko.appwebterminal.util.toLowerCaseUnder
11 |
12 | fun configureDataGeneration() {
13 | registrate.addDataGenerator(ProviderType.LANG, ::handleLang)
14 | }
15 |
16 | fun handleLang(langProvider: RegistrateLangProvider) {
17 | langProvider.add("appwebterminal.screen.title", "ME Web Terminal")
18 | langProvider.add("appwebterminal.button.done", "Done")
19 | langProvider.add("appwebterminal.hint.name", "Name: ")
20 | langProvider.add("appwebterminal.hint.password", "Password: ")
21 |
22 | langProvider.add("appwebterminal.message.render_complete", "Render complete.")
23 | langProvider.add("appwebterminal.message.rendering", "A renderer is currently working.")
24 | langProvider.add("appwebterminal.message.started", "Started renderer.")
25 |
26 | langProvider.add("appwebterminal.gui.me_network_online", "Online")
27 | langProvider.add("appwebterminal.gui.me_network_offline", "Offline")
28 |
29 | langProvider.add("config.screen.${AppWebTerminal.MOD_ID}", "AppliedWebTerminal Settings")
30 | dfs(langProvider, mutableSetOf(), AppWebTerminal.configHolder.valueMap)
31 | }
32 |
33 | private fun dfs(provider: RegistrateLangProvider, added: MutableSet, map: Map>) {
34 | for ((_, value) in map) {
35 | val id = value.id
36 | if (added.add(id)) {
37 | provider.add("config.${AppWebTerminal.MOD_ID}.option.$id", id.toLowerCaseUnder().toEnglishName())
38 | }
39 | if (value is ObjectValue) {
40 | dfs(provider, added, value.get())
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/networking/OpenWebTerminalScreenPacket.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.networking
2 |
3 | import icu.takeneko.appwebterminal.client.gui.WebTerminalScreen
4 | import net.minecraft.client.Minecraft
5 | import net.minecraft.core.BlockPos
6 | import net.minecraft.network.FriendlyByteBuf
7 | import net.minecraftforge.network.NetworkEvent
8 | import java.util.UUID
9 | import java.util.function.Supplier
10 |
11 | data class OpenWebTerminalScreenPacket(
12 | val initialName: String,
13 | val uuid: UUID,
14 | val blockPos: BlockPos,
15 | val password: String,
16 | val isOnline: Boolean
17 | ) {
18 | constructor(buf: FriendlyByteBuf) : this(
19 | buf.readUtf(),
20 | buf.readUUID(),
21 | buf.readBlockPos(),
22 | buf.readUtf(),
23 | buf.readBoolean()
24 | )
25 |
26 | fun encode(buf: FriendlyByteBuf) {
27 | buf.writeUtf(initialName)
28 | buf.writeUUID(uuid)
29 | buf.writeBlockPos(blockPos)
30 | buf.writeUtf(password)
31 | buf.writeBoolean(isOnline)
32 | }
33 |
34 | fun accept(context: Supplier) {
35 | context.get().enqueueWork {
36 | Minecraft.getInstance().setScreen(WebTerminalScreen(initialName, uuid, password, isOnline))
37 | }
38 | context.get().packetHandled = true
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/networking/UpdateWebTerminalNamePacket.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.networking
2 |
3 | import icu.takeneko.appwebterminal.support.AENetworkSupport
4 | import net.minecraft.network.FriendlyByteBuf
5 | import net.minecraftforge.network.NetworkEvent
6 | import java.util.UUID
7 | import java.util.function.Supplier
8 |
9 | data class UpdateWebTerminalNamePacket(
10 | val name: String,
11 | val uuid: UUID,
12 | val password: String
13 | ) {
14 | constructor(buf: FriendlyByteBuf) : this(buf.readUtf(), buf.readUUID(), buf.readUtf())
15 |
16 | fun encode(buf: FriendlyByteBuf) {
17 | buf.writeUtf(name)
18 | buf.writeUUID(uuid)
19 | buf.writeUtf(password)
20 | }
21 |
22 | fun accept(context: Supplier) {
23 | context.get().enqueueWork {
24 | AENetworkSupport.update(uuid, name, password)
25 | }
26 | context.get().packetHandled = true
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/resource/LanguageFileDownloader.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.resource
2 |
3 | import com.mojang.logging.LogUtils
4 | import icu.takeneko.appwebterminal.config.MinecraftAssetsApi
5 | import io.ktor.util.*
6 | import kotlinx.coroutines.future.await
7 | import kotlinx.coroutines.runBlocking
8 | import java.util.regex.Pattern
9 |
10 | class LanguageFileDownloader(val assetsSource: MinecraftAssetsApi) : Thread("LanguageFileDownloader") {
11 | private val logger = LogUtils.getLogger()
12 | private val minecraftVersion = MinecraftVersion(assetsSource)
13 | override fun run() {
14 | runBlocking {
15 | CacheProvider.init()
16 | logger.info("Updating minecraft versions.")
17 | minecraftVersion.update().whenComplete { _, u ->
18 | if (u != null) {
19 | logger.error("Failed to update Minecraft versions.", u)
20 | }
21 | }.await()
22 | logger.info("Resolving version asset indexes.")
23 | val versionAssetIndex = minecraftVersion.resolveVersionAssetIndex("1.20.1")!!.whenComplete { t, u ->
24 | if (u != null) {
25 | logger.error("Failed to resolve version assetIndex.", u)
26 | }
27 | }.await()
28 | versionAssetIndex.objects.entries.forEach { (name, obj) ->
29 | if (languageFilePattern.matcher(name).matches()) {
30 | val encodedName = name.encodeBase64()
31 | fileNameMapping[name] = encodedName
32 | logger.info("Downloading $name to $encodedName")
33 | for (i in 0 until 3) {
34 | try {
35 | CacheProvider.downloadFile(
36 | FileMetadata(
37 | encodedName,
38 | assetsSource.urlAssetsReplacer.apply(obj.downloadUrl),
39 | obj.size,
40 | obj.hash
41 | )
42 | )
43 | break
44 | } catch (e: Exception) {
45 | logger.error("Failed to download $name to $encodedName", e)
46 | continue
47 | }
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | companion object {
55 | private val languageFilePattern = Pattern.compile("minecraft/lang/(.+).json")
56 | val fileNameMapping = mutableMapOf()
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/resource/MinecraftVersion.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.resource
2 |
3 | import icu.takeneko.appwebterminal.config.MinecraftAssetsApi
4 | import io.ktor.http.*
5 | import io.ktor.util.*
6 | import kotlinx.serialization.json.Json
7 | import java.net.http.HttpClient
8 | import java.net.http.HttpRequest
9 | import java.net.http.HttpResponse.BodyHandlers
10 | import java.util.concurrent.CompletableFuture
11 |
12 | class MinecraftVersion(val assetsSource: MinecraftAssetsApi) {
13 | private val mojangApiUrl = assetsSource.endpoint
14 | private val Json = Json {
15 | ignoreUnknownKeys = true
16 | }
17 | private val versionManifestUrl = "$mojangApiUrl/mc/game/version_manifest_v2.json"
18 | lateinit var versionManifest: VersionManifest
19 | private val httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build()
20 | val versions = mutableMapOf()
21 |
22 | fun update(): CompletableFuture {
23 | val request = HttpRequest.newBuilder().GET().uri(Url(versionManifestUrl).toURI()).build()
24 | return httpClient.sendAsync(request, BodyHandlers.ofString()).thenApply {
25 | val resp = it.body()
26 | versionManifest = Json.decodeFromString(resp)
27 | for (version in versionManifest.versions) {
28 | versions[version.id] = version
29 | }
30 | return@thenApply versionManifest
31 | }
32 | }
33 |
34 | fun resolveVersionMetadata(versionName: String): CompletableFuture? {
35 | val version = versions[versionName] ?: return null
36 | val request = HttpRequest.newBuilder().GET().uri(Url(version.url).toURI()).build()
37 | return httpClient.sendAsync(request, BodyHandlers.ofString()).thenApply {
38 | val resp = it.body()
39 | return@thenApply Json.decodeFromString(resp)
40 | }
41 | }
42 |
43 | fun resolveVersionAssetIndex(version: String): CompletableFuture? {
44 | val versionMetadataFuture = resolveVersionMetadata(version) ?: return null
45 | return versionMetadataFuture.thenApply {
46 | HttpRequest.newBuilder().GET().uri(Url(assetsSource.urlIndexReplacer.apply(it.assetIndex.url)).toURI()).build()
47 | }.thenCompose {
48 | httpClient.sendAsync(it, BodyHandlers.ofString())
49 | }.thenApply {
50 | return@thenApply Json.decodeFromString(it.body())
51 | }
52 | }
53 | }
54 |
55 | @kotlinx.serialization.Serializable
56 | data class LatestData(val release: String, val snapshot: String)
57 |
58 | enum class VersionType {
59 | @kotlinx.serialization.SerialName("snapshot")
60 | SNAPSHOT,
61 |
62 | @kotlinx.serialization.SerialName("release")
63 | RELEASE,
64 |
65 | @kotlinx.serialization.SerialName("old_alpha")
66 | OLD_ALPHA,
67 |
68 | @kotlinx.serialization.SerialName("old_beta")
69 | OLD_BETA,
70 | }
71 |
72 | @kotlinx.serialization.Serializable
73 | data class AssetIndex(
74 | val objects: Map
75 | )
76 |
77 | @kotlinx.serialization.Serializable
78 | data class AssetObject(
79 | val hash: String,
80 | val size: Long
81 | ){
82 | val downloadUrl:String
83 | get() = "https://resources.download.minecraft.net/${hash.substring(0..1)}/$hash"
84 | }
85 |
86 | @kotlinx.serialization.Serializable
87 | data class VersionData(
88 | val id: String,
89 | val type: VersionType,
90 | val url: String,
91 | val releaseTime: String,
92 | val time: String
93 | )
94 |
95 | @kotlinx.serialization.Serializable
96 | data class VersionMetadata(
97 | val assetIndex: AssetIndexMetadata
98 | )
99 |
100 | @kotlinx.serialization.Serializable
101 | data class AssetIndexMetadata(
102 | val sha1: String,
103 | val size: Int,
104 | val url: String,
105 | val id: Int
106 | )
107 |
108 | @kotlinx.serialization.Serializable
109 | data class VersionManifest(val latest: LatestData, val versions: MutableList)
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/AENetworkAccess.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support
2 |
3 | import appeng.api.networking.IGrid
4 | import net.minecraft.core.BlockPos
5 | import net.minecraft.world.level.Level
6 | import java.util.UUID
7 |
8 | interface AENetworkAccess {
9 | fun getId(): UUID
10 |
11 | fun worldPosition(): BlockPos
12 |
13 | fun markDirty()
14 |
15 | fun getGrid(): IGrid?
16 |
17 | fun auth(password: String): Boolean
18 |
19 | fun update(displayName: String, password: String):Boolean
20 |
21 | fun validateNonce(nonce: String): Boolean
22 |
23 | fun getNonce(): String
24 |
25 | fun getTerminalName(): String
26 |
27 | fun level(): Level?
28 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/AENetworkSupport.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support
2 |
3 | import appeng.api.networking.IGrid
4 | import appeng.api.networking.security.IActionHost
5 | import icu.takeneko.appwebterminal.support.http.websocket.WebsocketSession
6 | import kotlinx.coroutines.runBlocking
7 | import net.minecraft.world.level.Level
8 | import org.slf4j.LoggerFactory
9 | import java.util.UUID
10 |
11 | object AENetworkSupport {
12 | val accessors: MutableMap = mutableMapOf()
13 | private val logger = LoggerFactory.getLogger("AE Network Support")
14 | private val sessions = mutableMapOf>()
15 |
16 | fun register(accessor: AENetworkAccess) {
17 | logger.info("Registering $accessor(${accessor.getId()})")
18 | accessors[accessor.getId()] = accessor
19 | }
20 |
21 | fun remove(accessor: AENetworkAccess) {
22 | if (accessor !in accessors.values) return
23 | logger.info("Unegistering $accessor(${accessor.getId()})")
24 | accessors.remove(accessor.getId())
25 | }
26 |
27 | fun notifySessionStarted(session: WebsocketSession) {
28 | logger.info("Starting new websocket session of {}({}).", session.owner, session.owner.getId())
29 | sessions.computeIfAbsent(session.owner) { mutableListOf() } += session
30 | }
31 |
32 | fun notifySessionTerminated(session: WebsocketSession) {
33 | logger.info("Terminating websocket session $session of ${session.owner}")
34 | sessions.computeIfAbsent(session.owner) { mutableListOf() } -= session
35 | }
36 |
37 | fun tick() {
38 | synchronized(sessions) {
39 | sessions.values.flatten().forEach {
40 | it.tick()
41 | }
42 | }
43 | }
44 |
45 | fun update(uuid: UUID, name: String, password: String) {
46 | if ((accessors[uuid] ?: return).update(name, password)) {
47 | logger.info("Updating $uuid password into $password")
48 | logger.info("Updating $uuid nonce into ${accessors[uuid]?.getNonce()}")
49 | }
50 | accessors[uuid]?.markDirty()
51 | logger.info("Renaming $uuid into $name")
52 | }
53 |
54 | fun requestSessionReset(accessor: AENetworkAccess) {
55 | val sessions = sessions.computeIfAbsent(accessor) { mutableListOf() }.toList()
56 | sessions.forEach { it.close() }
57 | this.sessions.computeIfAbsent(accessor) { mutableListOf() }.clear()
58 | }
59 |
60 | fun listAllTerminals(): List {
61 | return accessors.map { TerminalInfo(it.value.getTerminalName(), it.value.getId().toString()) }
62 | }
63 |
64 | fun validateNonce(uuid: UUID, nonce: String): Boolean {
65 | return (accessors[uuid] ?: return false).validateNonce(nonce)
66 | }
67 |
68 | fun reset() {
69 | accessors.clear()
70 | }
71 |
72 | fun auth(uuid: UUID, password: String): Boolean {
73 | return if (uuid in accessors) {
74 | accessors[uuid]!!.auth(password)
75 | } else {
76 | false
77 | }
78 | }
79 |
80 | fun getNonce(uuid: UUID): String {
81 | return (accessors[uuid] ?: throw IllegalArgumentException("No such accessor owns uuid $uuid")).getNonce()
82 | }
83 |
84 | fun getActionHost(uuid: UUID): IActionHost? {
85 | return (accessors[uuid] ?: return null) as? IActionHost
86 | }
87 |
88 | fun getGrid(uuid: UUID): IGrid? {
89 | return (accessors[uuid] ?: return null).getGrid()
90 | }
91 |
92 | fun getLevel(uuid: UUID): Level? {
93 | return (accessors[uuid] ?: return null).level()
94 | }
95 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/Data.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support
2 |
3 | import appeng.api.networking.crafting.CraftingJobStatus
4 | import appeng.api.networking.crafting.ICraftingCPU
5 | import appeng.api.stacks.AEKey
6 | import appeng.api.stacks.AEKeyType
7 | import appeng.api.stacks.GenericStack
8 | import appeng.api.stacks.KeyCounter
9 | import appeng.menu.me.crafting.CraftingStatus
10 | import appeng.menu.me.crafting.CraftingStatusEntry
11 | import icu.takeneko.appwebterminal.support.AEKeyObject.Companion.serializable
12 | import icu.takeneko.appwebterminal.support.MECraftingJobStatusBundle.Companion.serializable
13 | import icu.takeneko.appwebterminal.support.MECraftingStatusEntry.Companion.bundle
14 | import icu.takeneko.appwebterminal.support.MEStack.Companion.meStack
15 | import icu.takeneko.appwebterminal.util.ComponentSerializer
16 | import icu.takeneko.appwebterminal.util.ResourceLocationSerializer
17 | import icu.takeneko.appwebterminal.util.strip
18 | import net.minecraft.network.chat.Component
19 | import net.minecraft.resources.ResourceLocation
20 | import java.util.Objects
21 |
22 | @kotlinx.serialization.Serializable
23 | data class MECraftingStatusBundle(
24 | val fullStatus: Boolean,
25 | val elapsedTime: Long,
26 | val remainingItemCount: Long,
27 | val startItemCount: Long,
28 | val entries: List
29 | ) {
30 | companion object {
31 | val CraftingStatus.bundle: MECraftingStatusBundle?
32 | get() = if(this.entries.isEmpty()) null else MECraftingStatusBundle(
33 | this.isFullStatus,
34 | this.elapsedTime,
35 | this.remainingItemCount,
36 | this.startItemCount,
37 | this.entries.map { it.bundle }
38 | )
39 | }
40 | }
41 |
42 | @kotlinx.serialization.Serializable
43 | data class MECraftingStatusEntry(
44 | val serial: Long,
45 | val what: AEKeyObject?,
46 | val storedAmount: Long,
47 | val activeAmount: Long,
48 | val pendingAmount: Long
49 | ) {
50 | companion object {
51 | val CraftingStatusEntry.bundle: MECraftingStatusEntry
52 | get() = MECraftingStatusEntry(
53 | this.serial,
54 | this.what?.serializable(),
55 | this.storedAmount,
56 | this.activeAmount,
57 | this.pendingAmount
58 | )
59 | }
60 | }
61 |
62 | @kotlinx.serialization.Serializable
63 | data class MECpuStatusBundle(
64 | val id: Int,
65 | @kotlinx.serialization.Serializable(with = ComponentSerializer::class)
66 | val name: Component?,
67 | val busy: Boolean,
68 | val storageSize: Long,
69 | val coProcessorCount: Int,
70 | val craftingStatus: MECraftingJobStatusBundle?
71 | ) {
72 | companion object {
73 | fun ICraftingCPU.asStatus(id: Int): MECpuStatusBundle {
74 | return MECpuStatusBundle(
75 | id,
76 | this.name,
77 | this.isBusy,
78 | this.availableStorage,
79 | this.coProcessors,
80 | this.jobStatus?.serializable()
81 | )
82 | }
83 | }
84 | }
85 |
86 | @kotlinx.serialization.Serializable
87 | data class MECraftingJobStatusBundle(
88 | val crafting: MEStack,
89 | val totalItems: Long,
90 | val progress: Long,
91 | val elapsedTimeNanos: Long
92 | ) {
93 | companion object {
94 | fun CraftingJobStatus.serializable() = MECraftingJobStatusBundle(
95 | this.crafting.meStack,
96 | this.totalItems,
97 | this.progress,
98 | this.elapsedTimeNanos
99 | )
100 | }
101 | }
102 |
103 | @kotlinx.serialization.Serializable
104 | data class MEStack(
105 | val what: AEKeyObject,
106 | val amount: Long,
107 | var craftable: Boolean = false,
108 | ) {
109 | companion object {
110 | val GenericStack.meStack: MEStack
111 | get() = MEStack(this.what.serializable(), this.amount)
112 | val KeyCounter.meStacks: List
113 | get() = this.map { MEStack(it.key.serializable(), it.longValue) }
114 | }
115 | }
116 |
117 | @kotlinx.serialization.Serializable
118 | data class AEKeyTypeObject(
119 | @kotlinx.serialization.Serializable(with = ResourceLocationSerializer::class)
120 | val id: ResourceLocation,
121 | @kotlinx.serialization.Serializable(with = ComponentSerializer::class)
122 | val description: Component,
123 | ) {
124 | companion object {
125 | fun AEKeyType.serializable(): AEKeyTypeObject {
126 | return AEKeyTypeObject(
127 | this.id,
128 | this.description.strip()
129 | )
130 | }
131 | }
132 | }
133 |
134 | @kotlinx.serialization.Serializable
135 | data class AEKeyObject(
136 | @kotlinx.serialization.Serializable(with = ResourceLocationSerializer::class)
137 | val id: ResourceLocation,
138 | @kotlinx.serialization.Serializable(with = ComponentSerializer::class)
139 | val displayName: Component,
140 | @kotlinx.serialization.Serializable(with = ResourceLocationSerializer::class)
141 | val type: ResourceLocation,
142 | ) {
143 | companion object {
144 | fun AEKey.serializable(): AEKeyObject {
145 | return AEKeyObject(
146 | this.id,
147 | this.displayName.strip(),
148 | this.type.id
149 | )
150 | }
151 | }
152 |
153 | fun myHash(): Int {
154 | return Objects.hash(this.id, this.type)
155 | }
156 | }
157 |
158 | @kotlinx.serialization.Serializable
159 | data class TerminalInfo(val name: String, val uuid: String)
160 |
161 | @kotlinx.serialization.Serializable
162 | data class PageMeta(val total: Int, val page: Int, val limit: Int, val totalPages: Int)
163 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/MECraftingServiceView.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support
2 |
3 | import appeng.api.config.CpuSelectionMode
4 | import appeng.api.networking.IGrid
5 | import appeng.api.networking.crafting.ICraftingCPU
6 | import appeng.api.networking.crafting.ICraftingService
7 | import appeng.api.stacks.KeyCounter
8 | import appeng.me.cluster.implementations.CraftingCPUCluster
9 | import appeng.menu.me.common.IncrementalUpdateHelper
10 | import appeng.menu.me.crafting.CraftingStatus
11 | import com.google.common.collect.ImmutableSet
12 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainer
13 | import icu.takeneko.appwebterminal.api.crafting.CraftingCpuContainerHelper
14 | import icu.takeneko.appwebterminal.support.MECpuStatusBundle.Companion.asStatus
15 | import icu.takeneko.appwebterminal.support.MECraftingStatusBundle.Companion.bundle
16 | import icu.takeneko.appwebterminal.support.http.websocket.MECraftingServiceStatusBundle
17 |
18 | class MECraftingServiceView(
19 | val grid: IGrid,
20 | ) {
21 | private val craftingService: ICraftingService = grid.craftingService
22 | private var allCpus: ImmutableSet = craftingService.cpus
23 | private var cpuId = 1
24 | private val cpuMap = mutableMapOf()
25 | private var selectedCpuId = -1
26 | private var currentCpu: CraftingCpuContainer? = null
27 | private val incrementalUpdateHelper = IncrementalUpdateHelper()
28 | private val cpuUpdateListener = incrementalUpdateHelper::addChange
29 | private var cpuSchedulingMode = CpuSelectionMode.ANY
30 | private var craftingStatus: CraftingStatus? = null
31 | private var cantStoreItems: Boolean = false
32 | private var fullUpgradeSent = false
33 |
34 | init {
35 | allCpus.forEach {
36 | cpuMap[cpuId++] = it
37 | }
38 | }
39 |
40 | fun tick() {
41 | if (allCpus != craftingService.cpus) {
42 | allCpus = craftingService.cpus
43 | allCpus.forEach {
44 | if (it !in cpuMap.values) {
45 | cpuMap[cpuId++] = it
46 | }
47 | }
48 | cpuMap.entries.toList().forEach { (k, v) ->
49 | if (v !in allCpus) {
50 | cpuMap.remove(k)
51 | }
52 | }
53 | if (currentCpu?.unwrap() !in allCpus) {
54 | currentCpu = null
55 | craftingStatus = null
56 | }
57 | }
58 | if (this.currentCpu != null && fullUpgradeSent) {
59 | this.cpuSchedulingMode = this.currentCpu!!.selectionMode()
60 | this.cantStoreItems = this.currentCpu!!.cantStoreItems();
61 | this.craftingStatus = this.currentCpu!!.createCraftingStatus(this.incrementalUpdateHelper)
62 | this.incrementalUpdateHelper.commitChanges()
63 | }
64 | }
65 |
66 | fun selectCpu(id: Int) {
67 | if (id == -1) {
68 | this.selectedCpuId = -1
69 | this.currentCpu = null
70 | this.currentCpu?.removeUpdateListener(cpuUpdateListener)
71 | this.incrementalUpdateHelper.reset()
72 | this.fullUpgradeSent = false
73 | this.craftingStatus = null
74 | }
75 | this.cpuMap[id]?.let {
76 | this.selectedCpuId = id
77 | selectCpu(it)
78 | }
79 | }
80 |
81 | private fun selectCpu(cpu: ICraftingCPU) {
82 | this.currentCpu?.removeUpdateListener(cpuUpdateListener)
83 | this.incrementalUpdateHelper.reset()
84 | this.fullUpgradeSent = false
85 | this.craftingStatus = null
86 | val selection = CraftingCpuContainerHelper.create(cpu)
87 | if (selection != null) {
88 | this.currentCpu = selection
89 | val keyCounter = this.currentCpu!!.getAllItems()
90 | keyCounter.forEach {
91 | this.incrementalUpdateHelper.addChange(it.key)
92 | }
93 | this.currentCpu!!.addUpdateListener(cpuUpdateListener)
94 | this.craftingStatus = selection.createCraftingStatus(this.incrementalUpdateHelper)
95 | } else {
96 | this.currentCpu = null
97 | }
98 | }
99 |
100 | public fun getCpu(id: Int): ICraftingCPU? {
101 | return cpuMap[id]
102 | }
103 |
104 | fun createUpdateMessage(): MECraftingServiceStatusBundle {
105 | val data = MECraftingServiceStatusBundle(
106 | cpuMap.map { (k, v) -> v.asStatus(k) },
107 | craftingStatus?.bundle
108 | )
109 | if (data.craftingStatus?.entries != null) {
110 | fullUpgradeSent = true
111 | }
112 | return data
113 | }
114 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/Application.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http
2 |
3 | import icu.takeneko.appwebterminal.support.http.plugins.configureHTTP
4 | import icu.takeneko.appwebterminal.support.http.plugins.configureMonitoring
5 | import icu.takeneko.appwebterminal.support.http.plugins.configureSecurity
6 | import icu.takeneko.appwebterminal.support.http.plugins.configureSerialization
7 | import icu.takeneko.appwebterminal.support.http.plugins.configureSockets
8 | import icu.takeneko.appwebterminal.support.http.routing.configureRouting
9 | import io.ktor.server.application.*
10 | import io.ktor.server.cio.*
11 | import io.ktor.server.engine.*
12 | import org.slf4j.LoggerFactory
13 |
14 | class HttpServer(
15 | private val port: Int
16 | ) : Thread("WebTerminalHttpServer") {
17 | val logger = LoggerFactory.getLogger("HttpServer")
18 | val server = embeddedServer(
19 | CIO,
20 | port = port,
21 | host = "0.0.0.0",
22 | module = Application::module
23 | )
24 |
25 | override fun run() {
26 | try {
27 | logger.info("Starting server at port $port")
28 | server.start(true)
29 | } catch (e: Throwable) {
30 | if (e !is InterruptedException) {
31 | logger.error("Unable to launch http server.", e)
32 | }
33 | }
34 | }
35 |
36 | fun gracefullyStop() {
37 | logger.info("Stopping http server because minecraft server is stopping.")
38 | server.stop()
39 | }
40 | }
41 |
42 | fun Application.module() {
43 | configureSerialization()
44 | configureMonitoring()
45 | configureHTTP()
46 | configureSecurity()
47 | configureSockets()
48 | configureRouting()
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/HttpServerLifecycleSupport.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http
2 |
3 | object HttpServerLifecycleSupport {
4 | var serverInstance: HttpServer? = null
5 |
6 | fun launch(port: Int) {
7 | serverInstance?.interrupt()
8 | serverInstance = HttpServer(port)
9 | serverInstance!!.start()
10 | }
11 |
12 | fun stop() {
13 | serverInstance?.gracefullyStop()
14 | serverInstance = null
15 | }
16 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/plugins/HTTP.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.plugins
2 |
3 | import io.ktor.http.*
4 | import io.ktor.http.content.*
5 | import io.ktor.server.application.*
6 | import io.ktor.server.plugins.cachingheaders.*
7 | import io.ktor.server.plugins.cors.routing.*
8 | import io.ktor.server.plugins.forwardedheaders.*
9 |
10 | fun Application.configureHTTP() {
11 | install(ForwardedHeaders) // WARNING: for security, do not include this if not behind a reverse proxy
12 | install(XForwardedHeaders) // WARNING: for security, do not include this if not behind a reverse proxy
13 | install(CORS) {
14 | allowMethod(HttpMethod.Options)
15 | allowMethod(HttpMethod.Put)
16 | allowMethod(HttpMethod.Delete)
17 | allowMethod(HttpMethod.Patch)
18 | allowMethod(HttpMethod.Post)
19 | allowMethod(HttpMethod.Get)
20 | allowHeader(HttpHeaders.Authorization)
21 | allowHeader(HttpHeaders.ContentType)
22 | anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
23 | }
24 | install(CachingHeaders) {
25 | options { call, outgoingContent ->
26 | when (outgoingContent.contentType?.withoutParameters()) {
27 | ContentType.Text.CSS -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 24 * 60 * 60))
28 | ContentType.Image.PNG -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 7 * 24 * 60 * 60))
29 | else -> null
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/plugins/Monitoring.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.plugins
2 |
3 | import io.ktor.server.application.*
4 | import io.ktor.server.plugins.calllogging.*
5 | import io.ktor.server.request.*
6 | import org.slf4j.event.Level
7 |
8 | fun Application.configureMonitoring() {
9 | install(CallLogging) {
10 | level = Level.INFO
11 | filter { call -> call.request.path().startsWith("/") }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/plugins/Security.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.plugins
2 |
3 | import com.auth0.jwt.JWT
4 | import com.auth0.jwt.algorithms.Algorithm
5 | import com.auth0.jwt.interfaces.Payload
6 | import icu.takeneko.appwebterminal.support.AENetworkSupport
7 | import icu.takeneko.appwebterminal.util.queryJwt
8 | import io.ktor.http.*
9 | import io.ktor.server.application.*
10 | import io.ktor.server.auth.*
11 | import io.ktor.server.auth.jwt.*
12 | import io.ktor.server.response.*
13 | import io.ktor.util.*
14 | import org.slf4j.LoggerFactory
15 | import java.util.UUID
16 |
17 | const val jwtAudience = "WebTerminalFrontend"
18 | val jwtSecret = generateNonce()
19 | private val logger = LoggerFactory.getLogger("configureSecurity")
20 |
21 | private val jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecret))
22 | .withAudience(jwtAudience)
23 | .withIssuer("AppliedWebTerminal")
24 | .build()
25 |
26 | fun Application.configureSecurity() {
27 | // Please read the jwt property from the config file if you are using EngineMain
28 | logger.info("Using $jwtSecret as JWT secret.")
29 | install(Authentication) {
30 | jwt("jwt") {
31 | realm = "AppliedWebTerminal"
32 | verifier(jwtVerifier)
33 | validate { credential ->
34 | if (credential.payload.audience.contains(jwtAudience) && validateJwt(credential.payload))
35 | Principal(
36 | credential.payload,
37 | UUID.fromString(credential.payload.getClaim("uuid").asString()),
38 | credential.payload.getClaim("nonce").asString()
39 | )
40 | else
41 | null
42 | }
43 | challenge { defaultScheme, realm ->
44 | call.respond(
45 | HttpStatusCode.Unauthorized,
46 | "Token is not valid or has expired"
47 | )
48 | }
49 | }
50 | queryJwt("query_jwt") {
51 | verifier(
52 | JWT.require(Algorithm.HMAC256(jwtSecret))
53 | .withAudience(jwtAudience)
54 | .withIssuer("AppliedWebTerminal")
55 | .build()
56 | )
57 | validate { credential ->
58 | if (credential.payload.audience.contains(jwtAudience) && validateJwt(credential.payload))
59 | Principal(
60 | credential.payload,
61 | UUID.fromString(credential.payload.getClaim("uuid").asString()),
62 | credential.payload.getClaim("nonce").asString()
63 | )
64 | else
65 | null
66 | }
67 | challenge {
68 | call.respond(
69 | HttpStatusCode.Unauthorized,
70 | "Token is not valid or has expired"
71 | )
72 | }
73 | }
74 | }
75 | }
76 |
77 | fun validateJwt(payload: Payload): Boolean {
78 | val uuidClaim = payload.getClaim("uuid")
79 | val nonceClaim = payload.getClaim("nonce")
80 | if (uuidClaim.isNull || nonceClaim.isNull) return false
81 | return try {
82 | val uuid = UUID.fromString(uuidClaim.asString())
83 | val nonce = nonceClaim.asString()
84 | AENetworkSupport.validateNonce(uuid, nonce)
85 | } catch (e: IllegalArgumentException) {
86 | logger.warn("Could not validate jwt token: ", e)
87 | false
88 | }
89 | }
90 |
91 | class Principal(
92 | payload: Payload,
93 | val uuid: UUID,
94 | val nonce: String
95 | ) : JWTPayloadHolder(payload)
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/plugins/Serialization.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.plugins
2 |
3 | import io.ktor.serialization.kotlinx.json.*
4 | import io.ktor.server.application.*
5 | import io.ktor.server.plugins.contentnegotiation.*
6 | import io.ktor.server.response.*
7 | import io.ktor.server.routing.*
8 |
9 | fun Application.configureSerialization() {
10 | install(ContentNegotiation) {
11 | json()
12 | }
13 | routing {
14 | get("/json/kotlinx-serialization") {
15 | call.respond(mapOf("hello" to "world"))
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/plugins/Sockets.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.plugins
2 |
3 | import io.ktor.server.application.*
4 | import io.ktor.server.auth.*
5 | import io.ktor.server.routing.*
6 | import io.ktor.server.websocket.*
7 | import io.ktor.websocket.*
8 | import kotlin.time.Duration.Companion.seconds
9 |
10 | fun Application.configureSockets() {
11 | install(WebSockets) {
12 | pingPeriod = 15.seconds
13 | timeout = 15.seconds
14 | maxFrameSize = Long.MAX_VALUE
15 | masking = false
16 | }
17 | routing {
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/routing/FrontendSupportRouting.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.routing
2 |
3 | import icu.takeneko.appwebterminal.AppWebTerminal
4 | import icu.takeneko.appwebterminal.util.I18nUtil
5 | import icu.takeneko.appwebterminal.util.MinecraftI18nSupport
6 | import icu.takeneko.appwebterminal.util.ServerI18nSupport
7 | import icu.takeneko.appwebterminal.util.staticResourceForModContainer
8 | import io.ktor.http.*
9 | import io.ktor.server.application.*
10 | import io.ktor.server.response.*
11 | import io.ktor.server.routing.*
12 | import java.io.File
13 | import kotlin.io.path.Path
14 | import kotlin.io.path.div
15 | import kotlin.io.path.notExists
16 |
17 | fun Application.configureFrontendSupportRouting() {
18 | routing {
19 | staticResourceForModContainer()
20 | get("/settings") {
21 | return@get call.respond(
22 | FrontendSettings(
23 | AppWebTerminal.config.frontendTitle,
24 | AppWebTerminal.config.backendWebsocketEndpoint
25 | )
26 | )
27 | }
28 | get("/aeResource/{keyType}/{key...}") {
29 | val keyType = call.parameters["keyType"] ?: return@get
30 | val key = call.parameters.getAll("key")?.joinToString(File.separator) ?: return@get
31 | val filePath = Path("./aeKeyResources") /
32 | keyType.replace(":", "_") /
33 | "$key.png".replace(":", "_")
34 | if (filePath.notExists() || !filePath.normalize().toString().startsWith("aeKeyResources")) {
35 | return@get call.respond(HttpStatusCode.NotFound)
36 | }
37 | return@get call.respondFile(filePath.toFile())
38 | }
39 | get("/translate/{language}/{key...}") {
40 | val language = call.parameters["language"] ?: return@get call.respond(
41 | HttpStatusCode.BadRequest,
42 | "Expected parameter 'language'"
43 | )
44 | val key = call.parameters.getAll("key")?.joinToString(File.separator)
45 | ?: return@get call.respond(
46 | HttpStatusCode.BadRequest,
47 | "Expected parameter 'key'"
48 | )
49 | return@get call.respond(I18nUtil.get(language, key))
50 | }
51 | }
52 | }
53 |
54 | @kotlinx.serialization.Serializable
55 | private data class FrontendSettings(val title: String, val webSocketUrl: String)
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/routing/Routing.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.routing
2 |
3 | import io.ktor.server.application.*
4 |
5 | fun Application.configureRouting() {
6 | configureFrontendSupportRouting()
7 | configureAEServiceRouting()
8 | configureSecuritySupportRouting()
9 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/routing/SecuritySupportRouting.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.routing
2 |
3 | import com.auth0.jwt.JWT
4 | import com.auth0.jwt.algorithms.Algorithm
5 | import icu.takeneko.appwebterminal.support.AENetworkSupport
6 | import icu.takeneko.appwebterminal.support.http.plugins.Principal
7 | import icu.takeneko.appwebterminal.support.http.plugins.jwtAudience
8 | import icu.takeneko.appwebterminal.support.http.plugins.jwtSecret
9 | import io.ktor.server.application.*
10 | import io.ktor.server.auth.*
11 | import io.ktor.server.request.*
12 | import io.ktor.server.response.*
13 | import io.ktor.server.routing.*
14 | import java.time.ZoneOffset
15 | import java.time.format.DateTimeFormatter
16 | import java.util.Date
17 | import java.util.UUID
18 |
19 | fun Application.configureSecuritySupportRouting() {
20 | routing {
21 | post("/login") {
22 | return@post try {
23 | val user = call.receive()
24 | val uuid = UUID.fromString(user.uuid)
25 | if (AENetworkSupport.auth(uuid, user.password)) {
26 | val token = JWT.create()
27 | .withAudience(jwtAudience)
28 | .withIssuer("AppliedWebTerminal")
29 | .withClaim("uuid", user.uuid)
30 | .withClaim("nonce", AENetworkSupport.getNonce(uuid))
31 | .withExpiresAt(Date(System.currentTimeMillis() + 6000000))
32 | .sign(Algorithm.HMAC256(jwtSecret))
33 | call.respond(UserAuthResult(success = true, payload = token))
34 | } else {
35 | call.respond(UserAuthResult(success = false, payload = "Authentication failed."))
36 | }
37 | } catch (e: IllegalArgumentException) {
38 | call.respond(UserAuthResult(success = false, payload = e.message))
39 | }
40 | }
41 | authenticate("jwt") {
42 | get("/validate") {
43 | val principal = call.principal()
44 | return@get call.respond(
45 | if (principal != null) {
46 | val uuid = principal.payload.getClaim("uuid").asString()
47 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
48 | val expiresAt = principal.expiresAt?.toInstant()?.atZone(ZoneOffset.UTC)?.format(formatter)
49 | ValidateResult(true, uuid, expiresAt)
50 | } else {
51 | ValidateResult(false, null, null)
52 | }
53 | )
54 | }
55 | }
56 |
57 | }
58 | }
59 |
60 | @kotlinx.serialization.Serializable
61 | private data class ValidateResult(val success: Boolean, val uuid: String?, val expiresAt: String?)
62 |
63 | @kotlinx.serialization.Serializable
64 | private data class UserCredential(val uuid: String, val password: String)
65 |
66 | @kotlinx.serialization.Serializable
67 | private data class UserAuthResult(val success: Boolean, val payload: String?)
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/websocket/Protocol.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.websocket
2 |
3 | import icu.takeneko.appwebterminal.support.MECpuStatusBundle
4 | import icu.takeneko.appwebterminal.support.MECraftingStatusBundle
5 |
6 | interface Protocol {
7 | fun type(): String
8 | fun accept(session: WebsocketSession) {
9 | }
10 | }
11 |
12 | @kotlinx.serialization.Serializable
13 | data class SetUpdateInterval(val value: Int) : Protocol {
14 | override fun type(): String = "update_interval"
15 |
16 | override fun accept(session: WebsocketSession) {
17 | session.updateInterval = value
18 | session.updateCountdown = 0
19 | }
20 | }
21 |
22 | @kotlinx.serialization.Serializable
23 | data class SelectCpu(val cpuId: Int) : Protocol {
24 | override fun type(): String = "select_cpu"
25 |
26 | override fun accept(session: WebsocketSession) {
27 | session.craftingServiceView.selectCpu(cpuId)
28 | session.updateCountdown = 0
29 | }
30 | }
31 |
32 | @kotlinx.serialization.Serializable
33 | data class CancelJob(val cpuId: Int) : Protocol {
34 | override fun type(): String = "cancel_job"
35 | override fun accept(session: WebsocketSession) {
36 | session.craftingServiceView.getCpu(cpuId)?.cancelJob()
37 | }
38 | }
39 |
40 | @kotlinx.serialization.Serializable
41 | data class MECraftingServiceStatusBundle(
42 | val cpus: List,
43 | var craftingStatus: MECraftingStatusBundle?
44 | ) : Protocol {
45 | override fun type(): String = "status"
46 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/support/http/websocket/WebsocketSession.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.support.http.websocket
2 |
3 | import appeng.api.networking.IGrid
4 | import icu.takeneko.appwebterminal.support.AENetworkAccess
5 | import icu.takeneko.appwebterminal.support.AENetworkSupport
6 | import icu.takeneko.appwebterminal.support.MECraftingServiceView
7 | import icu.takeneko.appwebterminal.support.http.HttpServerLifecycleSupport
8 | import icu.takeneko.appwebterminal.util.DispatchedSerializer
9 | import io.ktor.websocket.*
10 | import kotlinx.coroutines.CancellationException
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.runBlocking
14 | import kotlinx.serialization.builtins.serializer
15 | import kotlinx.serialization.json.Json
16 | import org.slf4j.LoggerFactory
17 | import java.util.UUID
18 |
19 | class WebsocketSession(
20 | private val session: DefaultWebSocketSession,
21 | private val grid: IGrid,
22 | val owner: AENetworkAccess
23 | ) {
24 | private val Json = Json {
25 | ignoreUnknownKeys = true
26 | prettyPrint = false
27 | }
28 | internal var updateInterval = 20
29 | internal var updateCountdown = 0
30 | internal var craftingServiceView = MECraftingServiceView(grid)
31 | private val logger = LoggerFactory.getLogger("WebsocketSession")
32 | private val serializer = DispatchedSerializer(
33 | "type",
34 | mapOf(
35 | "update_interval" to SetUpdateInterval.serializer(),
36 | "select_cpu" to SelectCpu.serializer(),
37 | "status" to MECraftingServiceStatusBundle.serializer(),
38 | "cancel_job" to CancelJob.serializer(),
39 | ),
40 | String.serializer(),
41 | Protocol::type
42 | )
43 |
44 | fun tick() {
45 | if (updateCountdown-- <= 0) {
46 | craftingServiceView.tick()
47 | updateCountdown = updateInterval
48 | val message = craftingServiceView.createUpdateMessage()
49 | send(message)
50 | }
51 | }
52 |
53 | suspend fun accept() {
54 | AENetworkSupport.notifySessionStarted(this)
55 | try {
56 | for (frame in session.incoming) {
57 | if (frame is Frame.Text) {
58 | val text = frame.readText()
59 | logger.debug("Received $text.")
60 | try {
61 | val data = Json.decodeFromString(serializer, text)
62 | data.accept(this)
63 | } catch (e: Throwable) {
64 | logger.error("Error handling websocket packet:", e)
65 | }
66 | }
67 | }
68 | } catch (_: CancellationException) {
69 |
70 | } finally {
71 | AENetworkSupport.notifySessionTerminated(this)
72 | }
73 |
74 | }
75 |
76 | private fun send(message: T) where T : Protocol {
77 | val context = HttpServerLifecycleSupport.serverInstance?.server?.application?.coroutineContext
78 | ?: return
79 | CoroutineScope(context).launch {
80 | try {
81 | logger.debug("Sending {}", message)
82 | sendSuspend(message)
83 | } catch (_: CancellationException) {
84 |
85 | }
86 | }
87 | }
88 |
89 | private suspend fun sendSuspend(message: T) where T : Protocol {
90 | session.send(Json.encodeToString(serializer, message))
91 | }
92 |
93 | fun close() {
94 | val context = HttpServerLifecycleSupport.serverInstance?.server?.application?.coroutineContext
95 | ?: return
96 | CoroutineScope(context).launch {
97 | try {
98 | session.close(CloseReason(CloseReason.Codes.NORMAL, "Connection closed."))
99 | } catch (_: CancellationException) {
100 |
101 | }
102 |
103 | }
104 | }
105 | }
106 |
107 | fun main() {
108 |
109 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/AEUtil.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import appeng.api.stacks.AEKey
4 | import java.util.Objects
5 |
6 | fun AEKey.myHash(): Int {
7 | return Objects.hash(this.id, this.type.id)
8 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/CachedModFile.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import net.minecraftforge.forgespi.locating.IModFile
4 | import kotlin.io.path.readBytes
5 |
6 | class CachedModFile(
7 | private val modFile: IModFile
8 | ) {
9 | private val cacheMap = mutableMapOf()
10 |
11 | fun getResource(path: String): ByteArray? {
12 | val resourcePath = modFile.findResource(path) ?: return null
13 | return if (path in cacheMap) {
14 | cacheMap[path]
15 | } else {
16 | resourcePath.readBytes().also { cacheMap[path] = it }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/CommandUtils.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import com.mojang.brigadier.arguments.ArgumentType
4 | import com.mojang.brigadier.arguments.IntegerArgumentType
5 | import com.mojang.brigadier.arguments.StringArgumentType
6 | import com.mojang.brigadier.builder.LiteralArgumentBuilder
7 | import com.mojang.brigadier.builder.RequiredArgumentBuilder
8 | import com.mojang.brigadier.context.CommandContext as Ctx
9 | import net.minecraft.commands.CommandSourceStack
10 | import net.minecraft.network.chat.Component
11 | import kotlin.reflect.KProperty
12 |
13 | typealias S = CommandSourceStack
14 |
15 | fun CommandContext.sendFeedback(component: Component) {
16 | this.delegate.source.sendSystemMessage(component)
17 | }
18 |
19 | fun CommandContext.sendError(component: Component) {
20 | this.delegate.source.sendFailure(component)
21 | }
22 |
23 | class LiteralCommand(root: String) {
24 | val node: LiteralArgumentBuilder = LiteralArgumentBuilder.literal(root)
25 | }
26 |
27 | class ArgumentCommand(name: String, argumentType: ArgumentType) {
28 | val node: RequiredArgumentBuilder =
29 | RequiredArgumentBuilder.argument(name, argumentType)
30 | }
31 |
32 |
33 | fun LiteralCommand(literal: String, function: LiteralCommand.() -> Unit): LiteralCommand {
34 | return LiteralCommand(literal).apply(function)
35 | }
36 |
37 | fun LiteralCommand.literal(literal: String, function: LiteralCommand.() -> Unit) {
38 | this.node.then(LiteralCommand(literal).apply(function).node)
39 | }
40 |
41 | fun LiteralCommand.requires(predicate: (S) -> Boolean, function: LiteralCommand.() -> Unit) {
42 | this.apply {
43 | node.requires(predicate)
44 | function(this)
45 | }
46 | }
47 |
48 | fun ArgumentCommand.requires(predicate: (S) -> Boolean, function: ArgumentCommand.() -> Unit) {
49 | this.apply {
50 | node.requires(predicate)
51 | function(this)
52 | }
53 | }
54 |
55 | fun LiteralCommand.execute(function: CommandContext.() -> Int) {
56 | this.node.executes {
57 | CommandContext(it).function()
58 | }
59 | }
60 |
61 | fun LiteralCommand.requires(function: S.() -> Boolean): LiteralCommand {
62 | this.node.requires(function)
63 | return this
64 | }
65 |
66 | fun LiteralCommand.argument(name: String, argumentType: ArgumentType, function: ArgumentCommand.() -> Unit) {
67 | this.node.then(ArgumentCommand(name, argumentType).apply(function).node)
68 | }
69 |
70 |
71 | fun ArgumentCommand.literal(literal: String, function: LiteralCommand.() -> Unit) {
72 | this.node.then(LiteralCommand(literal).apply(function).node)
73 | }
74 |
75 | fun ArgumentCommand.execute(function: CommandContext.() -> Int) {
76 | this.node.executes {
77 | CommandContext(it).function()
78 | }
79 | }
80 |
81 | fun ArgumentCommand.requires(function: S.() -> Boolean): ArgumentCommand {
82 | this.node.requires(function)
83 | return this
84 | }
85 |
86 | fun ArgumentCommand.argument(
87 | name: String,
88 | argumentType: ArgumentType,
89 | function: ArgumentCommand.() -> Unit
90 | ) {
91 | this.node.then(ArgumentCommand(name, argumentType).apply(function).node)
92 | }
93 |
94 | fun ArgumentCommand.integerArgument(
95 | name: String,
96 | min: Int = Int.MIN_VALUE,
97 | max: Int = Int.MAX_VALUE,
98 | function: ArgumentCommand.() -> Unit
99 | ) {
100 | this.node.then(ArgumentCommand(name, IntegerArgumentType.integer(min, max)).apply(function).node)
101 | }
102 |
103 | fun ArgumentCommand.wordArgument(
104 | name: String,
105 | function: ArgumentCommand.() -> Unit
106 | ) {
107 | this.node.then(ArgumentCommand(name, StringArgumentType.word()).apply(function).node)
108 | }
109 |
110 | fun ArgumentCommand.greedyStringArgument(
111 | name: String,
112 | function: ArgumentCommand.() -> Unit
113 | ) {
114 | this.node.then(ArgumentCommand(name, StringArgumentType.greedyString()).apply(function).node)
115 | }
116 |
117 |
118 | fun LiteralCommand.integerArgument(
119 | name: String,
120 | min: Int = Int.MIN_VALUE,
121 | max: Int = Int.MAX_VALUE,
122 | function: ArgumentCommand.() -> Unit
123 | ) {
124 | this.node.then(ArgumentCommand(name, IntegerArgumentType.integer(min, max)).apply(function).node)
125 | }
126 |
127 | fun LiteralCommand.wordArgument(
128 | name: String,
129 | function: ArgumentCommand.() -> Unit
130 | ) {
131 | this.node.then(ArgumentCommand(name, StringArgumentType.word()).apply(function).node)
132 | }
133 |
134 | fun LiteralCommand.greedyStringArgument(
135 | name: String,
136 | function: ArgumentCommand.() -> Unit
137 | ) {
138 | this.node.then(ArgumentCommand(name, StringArgumentType.greedyString()).apply(function).node)
139 | }
140 |
141 | class CommandContext(
142 | val delegate: Ctx
143 | ) {
144 | inline operator fun getValue(thisRef: Any?, prop: KProperty<*>): T {
145 | if (T::class.java == Ctx::class.java) {
146 | return delegate as T
147 | }
148 | if (T::class.java == S::class.java){
149 | return delegate.source as T
150 | }
151 | return delegate.getArgument(prop.name, T::class.java)
152 | }
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/FormattingUtil.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import appeng.shaded.flatbuffers.reflection.Object
4 | import com.google.common.base.CaseFormat
5 | import net.minecraft.network.chat.Component
6 | import net.minecraft.network.chat.ComponentContents
7 | import net.minecraft.network.chat.MutableComponent
8 | import net.minecraft.network.chat.contents.LiteralContents
9 | import net.minecraft.network.chat.contents.TranslatableContents
10 | import java.util.Locale
11 |
12 | fun String.toEnglishName(): String {
13 | return this.lowercase(Locale.ROOT).split("_")
14 | .joinToString(separator = " ") { a ->
15 | a.replaceFirstChar {
16 | if (it.isLowerCase()) {
17 | it.titlecase(Locale.getDefault())
18 | } else {
19 | it.toString()
20 | }
21 | }
22 | }
23 | }
24 |
25 | fun String.toLowerCaseUnder(): String {
26 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, this)
27 | }
28 |
29 | fun Component.toLocalizedString(lang: String): String {
30 | val contents = this.contents
31 | var rt = when (contents) {
32 | is LiteralContents -> {
33 | contents.text()
34 | }
35 |
36 | is TranslatableContents -> {
37 |
38 | val key = contents.key
39 | val args = contents.args.map {
40 | if (it is Component) {
41 | return@map it.toLocalizedString(lang)
42 | }
43 | return@map it.toString()
44 | }
45 | I18nUtil.translate(lang, key, *args.toTypedArray())
46 | }
47 |
48 | else -> {
49 | ""
50 | }
51 | }
52 | this.siblings.forEach {
53 | rt += it.toLocalizedString(lang)
54 | }
55 | return rt
56 | }
57 |
58 |
59 | fun Component.strip(): MutableComponent {
60 | val rt: MutableComponent
61 | if (neededContents(this.contents)) {
62 | rt = MutableComponent.create(this.contents)
63 | } else {
64 | rt = MutableComponent.create(ComponentContents.EMPTY)
65 | }
66 | rt.style = this.style.withHoverEvent(null).withClickEvent(null).withInsertion(null)
67 | this.siblings.forEach { c -> rt.append(c.strip()) }
68 | return rt
69 | }
70 |
71 | private fun neededContents(contents: ComponentContents): Boolean {
72 | if (contents == ComponentContents.EMPTY) return true
73 | if (contents is LiteralContents) return true
74 | if (contents is TranslatableContents) return true
75 | return false
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/I18nUtil.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import java.util.IllegalFormatException
4 |
5 | object I18nUtil {
6 | val languageProviders = listOf<(String, String) -> String?>(
7 | { lang, key -> KubejsI18nSupport.get(lang, key) },
8 | { lang, key -> MinecraftI18nSupport.get(lang, key) },
9 | { lang, key -> ServerI18nSupport.get(lang, key) }
10 | )
11 |
12 | fun get(language: String, key: String): String {
13 | val content = languageProviders.asSequence()
14 | .map { it(language, key) }
15 | .filterNotNull()
16 | .firstOrNull()
17 | return content ?: key
18 | }
19 |
20 | fun translate(language: String, key: String, vararg args: Any?): String {
21 | val content = get(language, key)
22 | return if (args.isNotEmpty()) {
23 | try {
24 | content.format(*args)
25 | } catch (e: IllegalFormatException) {
26 | key
27 | }
28 | } else {
29 | content
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/KRegistrate.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import com.tterrag.registrate.Registrate
4 | import net.minecraftforge.eventbus.api.IEventBus
5 | import thedarkcolour.kotlinforforge.forge.MOD_BUS
6 |
7 | class KRegistrate(modid: String) : Registrate(modid) {
8 |
9 | private lateinit var modEventBus: IEventBus
10 |
11 | override fun getModEventBus(): IEventBus {
12 | return modEventBus
13 | }
14 |
15 | override fun registerEventListeners(bus: IEventBus): Registrate {
16 | this.modEventBus = bus;
17 | return super.registerEventListeners(bus)
18 | }
19 |
20 | companion object {
21 | fun create(modid: String): Registrate {
22 | return KRegistrate(modid).apply {
23 | this.registerEventListeners(MOD_BUS)
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/kotlin/icu/takeneko/appwebterminal/util/KubejsI18nSupport.kt:
--------------------------------------------------------------------------------
1 | package icu.takeneko.appwebterminal.util
2 |
3 | import com.google.common.collect.HashBasedTable
4 | import com.mojang.logging.LogUtils
5 | import kotlinx.serialization.json.Json
6 | import kotlinx.serialization.json.JsonDecoder
7 | import kotlin.io.path.Path
8 | import kotlin.io.path.div
9 | import kotlin.io.path.exists
10 | import kotlin.io.path.listDirectoryEntries
11 | import kotlin.io.path.name
12 | import kotlin.io.path.nameWithoutExtension
13 |
14 | object KubejsI18nSupport {
15 | private const val DEFAULT_LANGUAGE = "en_us"
16 | private val languages = HashBasedTable.create()
17 | private val logger = LogUtils.getLogger()
18 |
19 | private val Json = Json {
20 | ignoreUnknownKeys = true
21 | }
22 |
23 | fun init() {
24 | val basePath = Path("kubejs", "assets")
25 | if (!basePath.exists()) return@init
26 | basePath.listDirectoryEntries().forEach {
27 | val langDir = it / "lang"
28 | if (!langDir.exists()) return@init
29 | langDir.listDirectoryEntries("*.json").forEach { langFile ->
30 | val langName = langFile.nameWithoutExtension
31 | try {
32 | val map = Json.decodeFromString