├── .github └── workflows │ └── commitTest.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_zh_CN.md ├── SyncBundles.java ├── assets ├── bundles │ ├── bundle.properties │ ├── bundle_de_DE.properties │ ├── bundle_id_ID.properties │ ├── bundle_ja.properties │ ├── bundle_ru.properties │ ├── bundle_zh_CN.properties │ └── bundle_zh_TW.properties ├── config │ └── mod_config.hjson ├── shaders │ ├── dist_base.frag │ └── entity_range.frag └── sprites │ └── ui │ ├── background.9.png │ ├── healthbar.9.png │ ├── icons │ ├── helium.png │ ├── java.png │ ├── javascript.png │ ├── network-error.png │ ├── program.png │ └── slots-back.png │ └── shieldbar.9.png ├── build.gradle.kts ├── bundleInfo.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.png ├── ktstools ├── genReflectCstrWrap.kts ├── genReflectCstrs.kts ├── genReflectMetWrap.kts └── genReflectMethods.kts ├── mod.hjson ├── preview_imgs ├── en │ ├── attackRange.png │ ├── blur-1.png │ ├── blur-2.png │ ├── configEntry.png │ ├── configurePane.png │ ├── control-button.png │ ├── effectRange.png │ ├── modBrowser.png │ ├── modManager.png │ ├── placement-fold.png │ ├── placement-unfold.png │ ├── quick-config-entry.png │ ├── quick-config.png │ └── statusDisplay.png └── zh_CN │ ├── attackRange.png │ ├── blur-1.png │ ├── blur-2.png │ ├── configEntry.png │ ├── configurePane.png │ ├── control-button.png │ ├── effectRange.png │ ├── modBrowser.png │ ├── modManager.png │ ├── placement-fold.png │ ├── placement-unfold.png │ ├── quick-config-entry.png │ ├── quick-config.png │ └── statusDisplay.png ├── settings.gradle.kts └── src └── main └── kotlin └── helium ├── He.kt ├── HeConfig.kt ├── Helium.kt ├── graphics ├── BaseClipDrawable.kt ├── BaseStripDrawable.kt ├── Blur.kt ├── ClipDrawable.kt ├── DrawUtils.kt ├── EdgeLineStripDrawable.kt ├── FillStripDrawable.kt ├── HeShaders.kt ├── LazyDrawable.kt ├── NinePatchClipDrawable.kt ├── ScaledNinePatchClipDrawable.kt ├── ScreenSampler.kt ├── StripDrawable.kt └── g2d │ └── EntityRangeExtractor.kt ├── ui ├── HeAssets.kt ├── HeStyles.kt ├── UIUtils.kt ├── dialogs │ ├── ConfigLayouts.kt │ ├── HeModsDialog.kt │ ├── ModConfigDialog.kt │ └── modpacker │ │ ├── MdtMetaGen.kt │ │ ├── MetaGenerator.kt │ │ ├── ModInfo.kt │ │ └── PackModel.kt ├── elements │ ├── HeCollapser.kt │ ├── SwappableCell.kt │ └── roulette │ │ ├── Roulette.kt │ │ ├── Strip.kt │ │ ├── StripButton.kt │ │ ├── StripElement.kt │ │ └── StripWrap.kt └── fragments │ ├── entityinfo │ ├── ConfigurableDisplay.kt │ ├── EntityInfoDisplay.kt │ ├── EntityInfoFrag.kt │ ├── EntityInfoStyles.kt │ └── displays │ │ ├── DetailsDisplay.kt │ │ ├── EntityRangeDisplay.kt │ │ ├── HealthDisplay.kt │ │ └── StatusDisplay.kt │ └── placement │ └── HePlacementFrag.kt └── util ├── Downloader.kt ├── Formatter.kt ├── Geomtry.kt ├── Reflection.kt ├── Structs.kt └── binds ├── CombineKeyListener.kt ├── CombineKeyTree.kt └── CombinedKeys.kt /.github/workflows/commitTest.yml: -------------------------------------------------------------------------------- 1 | name: Build Mod 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | buildJar: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up PATH 12 | run: | 13 | echo "${ANDROID_HOME}/build-tools/34.0.0" >> $GITHUB_PATH 14 | - name: Set up JDK 17 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 17 18 | - name: Build mod jar 19 | run: | 20 | chmod +x ./gradlew 21 | ./gradlew deploy 22 | - name: Upload built jar file 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: ${{ github.event.repository.name }} 26 | path: build/libs/${{ github.event.repository.name }}.jar 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | /core/assets/mindustry-saves/ 3 | /core/assets/mindustry-maps/ 4 | /core/assets/bundles/output/ 5 | /core/assets/.gifimages/ 6 | /deploy/ 7 | /desktop/packr-out/ 8 | /desktop/packr-export/ 9 | /desktop/mindustry-saves/ 10 | /desktop/mindustry-maps/ 11 | /desktop/gifexport/ 12 | /core/lib/ 13 | /ios/assets/ 14 | /core/assets-raw/sprites/generated/ 15 | /core/assets-raw/sprites_out/ 16 | /annotations/build/ 17 | /annotations/out/ 18 | /net/build/ 19 | /tools/build/ 20 | /tests/build/ 21 | /server/build/ 22 | /test_files/ 23 | /annotations/build/ 24 | /desktop-sdl/build/ 25 | desktop-sdl/build/ 26 | /android/assets/mindustry-maps/ 27 | /android/assets/mindustry-saves/ 28 | /core/assets/gifexport/ 29 | /core/assets/version.properties 30 | /core/assets/locales 31 | /ios/src/io/anuke/mindustry/gen/ 32 | /core/src/io/anuke/mindustry/gen/ 33 | ios/robovm.properties 34 | packr-out/ 35 | *.gif 36 | 37 | version.properties 38 | 39 | .attach_* 40 | ## Java 41 | 42 | *.class 43 | *.war 44 | *.ear 45 | hs_err_pid* 46 | crash-report-* 47 | replay_pid* 48 | 49 | ## Robovm 50 | /ios/robovm-build/ 51 | 52 | ## GWT 53 | /html/war/ 54 | /html/gwt-unitCache/ 55 | .apt_generated/ 56 | .gwt/ 57 | gwt-unitCache/ 58 | www-test/ 59 | .gwt-tmp/ 60 | 61 | ## Android Studio and Intellij and Android in general 62 | /android/libs/armeabi/ 63 | /android/libs/armeabi-v7a/ 64 | /android/libs/arm64-v8a/ 65 | /android/libs/x86/ 66 | /android/libs/x86_64/ 67 | /android/gen/ 68 | .idea/ 69 | *.ipr 70 | *.iws 71 | *.iml 72 | /android/out/ 73 | com_crashlytics_export_strings.xml 74 | 75 | ## Eclipse 76 | 77 | .classpath 78 | .project 79 | .metadata/ 80 | /android/bin/ 81 | /core/bin/ 82 | /desktop/bin/ 83 | /html/bin/ 84 | /ios/bin/ 85 | /ios-moe/bin/ 86 | *.tmp 87 | *.bak 88 | *.swp 89 | *~.nib 90 | .settings/ 91 | .loadpath 92 | .externalToolBuilders/ 93 | *.launch 94 | 95 | ## NetBeans 96 | 97 | /nbproject/private/ 98 | /android/nbproject/private/ 99 | /core/nbproject/private/ 100 | /desktop/nbproject/private/ 101 | /html/nbproject/private/ 102 | /ios/nbproject/private/ 103 | /ios-moe/nbproject/private/ 104 | 105 | /build/ 106 | /android/build/ 107 | /core/build/ 108 | /desktop/build/ 109 | /html/build/ 110 | /ios/build/ 111 | /ios-moe/build/ 112 | 113 | /nbbuild/ 114 | /android/nbbuild/ 115 | /core/nbbuild/ 116 | /desktop/nbbuild/ 117 | /html/nbbuild/ 118 | /ios/nbbuild/ 119 | /ios-moe/nbbuild/ 120 | 121 | /dist/ 122 | /android/dist/ 123 | /core/dist/ 124 | /desktop/dist/ 125 | /html/dist/ 126 | /ios/dist/ 127 | /ios-moe/dist/ 128 | 129 | /nbdist/ 130 | /android/nbdist/ 131 | /core/nbdist/ 132 | /desktop/nbdist/ 133 | /html/nbdist/ 134 | /ios/nbdist/ 135 | /ios-moe/nbdist/ 136 | 137 | nbactions.xml 138 | nb-configuration.xml 139 | 140 | ## Gradle 141 | 142 | /local.properties 143 | .gradle/ 144 | gradle-app.setting 145 | /build/ 146 | /android/build/ 147 | /core/build/ 148 | /desktop/build/ 149 | /html/build/ 150 | /ios/build/ 151 | /ios-moe/build/ 152 | 153 | ## OS Specific 154 | .DS_Store 155 | Thumbs.db 156 | android/libs/ 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helium 2 | 3 | > **点击[>> 这里 <<](README_zh_CN.md)查看中文文档** 4 | 5 | 6 | mod icon 7 | 8 | A Mindustry UI optimization mod that provides aesthetic enhancements and a more intuitive interface to improve gameplay experience. 9 | 10 | ### Core Features 11 | 12 | Currently in early development with primary features including: 13 | 14 | - **Default Dialog Background Blur (Frosted Glass Effect)** 15 | 16 | The Helium mod introduces an elegant blurred transparent background for in-game dialogs (currently dynamic frosted glass effect, with potential plans for static blur backgrounds if optimization demands arise) 17 | 18 | ![Blur Effect](preview_imgs/en/blur-1.png) 19 | ![Blur Effect](preview_imgs/en/blur-2.png) 20 | 21 | - **In-Game Entity Information Components** 22 | 23 | Provides various UI elements for game entities including units and blocks: 24 | 25 | - **Health & Status Indicators**: 26 | 27 | Displays real-time status (health, shields) above units and entities. When shield stacks exceed max health, multi-layer shields with stack counters are shown. 28 | 29 | ![Status Display](preview_imgs/en/statusDisplay.png) 30 | 31 | - **Attack Range Indicators**: 32 | 33 | Units and turrets now visualize their attack ranges as translucent areas. Processed boundaries emphasize combined team range contours to prevent visual clutter, with subtle pulsating animations and chromatic borders. 34 | 35 | ![Attack Range](preview_imgs/en/attackRange.png) 36 | 37 | - **Functional Block Effect Range Indicators**: 38 | 39 | Devices like Mend Projectors, Overdrive Projectors, and unit repair stations display effect ranges with distinctive colors. 40 | 41 | ![Effect Range](preview_imgs/en/effectRange.png) 42 | 43 | Hold the control hotkey (default: Left Alt key on keyboards, or a toolbar button on Android devices) to display detailed information such as unit weapon attack angles and entity details (since optimization panels may disable hover info on block placement panels, holding the hotkey allows targeted selection): 44 | 45 | ![Control Button](preview_imgs/en/control-button.png) 46 | 47 | All features can be configured via the quick settings panel to control visibility per team. Disabled teams will not display corresponding elements. Access the quick configuration tool via the panel button: 48 | 49 | ![Info Display Quick Config Entry](preview_imgs/en/quick-config-entry.png) 50 | 51 | Configuration panel: 52 | 53 | ![Quick Configuration Panel](preview_imgs/en/quick-config.png) 54 | 55 | - **Enhanced Block Placement Panel** 56 | 57 | A refined block selection panel integrating a quick item bar and standardized tool buttons. The panel can be collapsed to show only the quick item bar and toolbar, minimizing HUD clutter. 58 | 59 | The quick item bar supports pagination and allows users to pin frequently used items for rapid access while the panel is collapsed. 60 | 61 | ![Placement Panel (Collapsed)](preview_imgs/en/placement-fold.png) 62 | ![Placement Panel (Expanded)](preview_imgs/en/placement-unfold.png) 63 | 64 | - **The better Mod Manager and Mod Browser** 65 | 66 | Reworked the Mods Manager and Mods Browser, now they looks more aesthetically pleasing and functional, you can normally open [Mods] page to open new Mod Manager when it enabled, now the mods manager looks like: 67 | 68 | ![mod管理器](preview_imgs/en/modManager.png) 69 | ![mod管理器](preview_imgs/en/modBrowser.png) 70 | 71 | ### Mod Configuration 72 | 73 | Added **_\[Helium Config]_** entry in game settings, providing modular control over all UI modifications: 74 | 75 | ![Config Entry](preview_imgs/en/configEntry.png) 76 | ![Configuration Interface](preview_imgs/en/configurePane.png) 77 | 78 | ### Development Roadmap 79 | 80 | Current Priorities: 81 | - [x] In-game information panel quick toggle controls 82 | - [ ] Enhanced HUD elements (wave info panel, block selection bar) 83 | - [x] (Partially complete) Improved quick-access item bar with smart recommendations 84 | - [x] Standardized quick toolbar for better UI button placement 85 | - [ ] Redesigned mod configuration interface 86 | 87 | Future Plans: 88 | - [ ] Static background blur implementation 89 | - [ ] Overhauled game settings interface 90 | - [ ] Quick Schematic panel 91 | - [ ] Performance-optimized attack/effect range indicators (lower quality variant) -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # Helium(氦) 2 | 3 | 4 | mod icon 5 | 6 | 一个Mindustry的UI优化mod,提供美化的和更加清晰的界面以优化游戏体验。 7 | 8 | ### 主要功能 9 | 10 | 目前较早期开发进度较低,主要包含功能: 11 | 12 | - **默认对话框背景模糊(毛玻璃效果)** 13 | 14 | 氦模组为游戏提供了一个更美观的对话框模糊透明背景(目前为动态毛玻璃,后续如果存在对优化的需求可能增加静态背景模糊的计划) 15 | 16 | ![背景模糊](preview_imgs/zh_CN/blur-1.png) 17 | ![背景模糊](preview_imgs/zh_CN/blur-2.png) 18 | 19 | - **游戏内实体信息显示组件** 20 | 21 | 氦模组还为游戏内的各类实体如单位和方块,提供了生命条,状态栏以及攻击范围指示器等部件 22 | 23 | - **生命及状态指示器**: 24 | 25 | 单位及大部分实体顶部会显示其当前状态,包括生命值,护盾值(当护盾叠加超过最大生命值时会显示为多层护盾,并附加一个护盾倍数指示护盾的层数) 26 | 27 | ![状态显示](preview_imgs/zh_CN/statusDisplay.png) 28 | 29 | - **攻击范围指示器**: 30 | 31 | 所有的单位和炮塔现在会将它们的攻击范围以一个透明区域显示出来,边界经过处理后会只强调同一队伍叠加的攻击范围的外缘轮廓,以避免过多的叠加使得画面混乱,各个单位/炮塔的攻击范围也会有一个不那么显眼的波动动画与带有色差的边界去指示出来 32 | 33 | ![攻击范围](preview_imgs/zh_CN/attackRange.png) 34 | 35 | - **功能性方块效果范围指示器**: 36 | 37 | 诸如修复投影,超速投影以及单位的维修站等设备的左右范围会像攻击范围一样被显示出来,并且具有特定的颜色 38 | 39 | ![效果范围](preview_imgs/zh_CN/effectRange.png) 40 | 41 | 通过按住控制热键(默认为键盘左Alt,或者在安卓设备上是工具栏的按钮)显示更详细的信息,如单位武器的攻击角,单位的详细信息(由于优化面板会取消掉方块放置面板上的悬停信息,则可以通过按住控制热键来选中目标显示详细信息): 42 | 43 | ![控制按钮](preview_imgs/zh_CN/control-button.png) 44 | 45 | 所有功能均可通过快速配置面板配置其显示的队伍,被禁用的队伍则不会在对应功能上显示,快速设置工具通过快捷面板上的按钮打开: 46 | 47 | ![信息显示快速配置入口](preview_imgs/zh_CN/quick-config-entry.png) 48 | 49 | 配置面板如下: 50 | 51 | ![显示快速配置面板](preview_imgs/zh_CN/quick-config.png) 52 | 53 | - **更好的方块放置面板** 54 | 55 | 一个更好的方块选择面板,集成了快速物品栏和更规范化的工具按钮表,寻常时可以折叠起来只显示快速物品栏和工具栏以减少HUD上的信息量。 56 | 57 | 快捷物品栏可翻页,可选择常用的物品放入其中,以便在选择栏折叠状态下快速选择需要的方块。 58 | 59 | ![放置面板](preview_imgs/zh_CN/placement-fold.png) 60 | ![放置面板](preview_imgs/zh_CN/placement-unfold.png) 61 | 62 | - **更好的mod管理器和浏览器** 63 | 64 | 重做游戏的Mod管理器与浏览器,现在它看起来更美观且实用,在启用状态下打开[模组]页面即可进入新的管理界面,它现在看起来是这样的: 65 | 66 | ![mod管理器](preview_imgs/zh_CN/modManager.png) 67 | ![mod管理器](preview_imgs/zh_CN/modBrowser.png) 68 | 69 | ### 模组配置 70 | 71 | 游戏内设置菜单新增了一个 **_\[氦配置]_** 选项,该子栏目可以进入氦模组的相关配置界面,模组内所有对游戏界面的调整及优化均会模块化,并可便捷的在此处进行设置。 72 | 73 | ![配置入口](preview_imgs/zh_CN/configEntry.png) 74 | ![配置界面](preview_imgs/zh_CN/configurePane.png) 75 | 76 | ### 开发计划 77 | 78 | 目前的主要计划内容 79 | 80 | - [x] 局内信息面板快速切换操作 81 | - [ ] 更好的局内HUD(左上角波次信息,右下角的方块选择栏) 82 | - [x] (部分完成) 更好的快捷物品栏和智能推荐栏,将取代游戏本身的方块选择栏 83 | - [x] 更规范的快速工具栏,以更好的在游戏界面内放置操作按钮 84 | - [x] 重做mod设置界面 85 | 86 | 未来预期的开发计划 87 | 88 | - [ ] 静态的背景模糊 89 | - [ ] 重做游戏设置界面 90 | - [ ] 快速蓝图 91 | - [ ] 较低质量换取性能的攻击/效果范围指示器 92 | -------------------------------------------------------------------------------- /SyncBundles.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.util.ArrayList; 3 | import java.util.LinkedHashMap; 4 | import java.util.Locale; 5 | import java.util.regex.Pattern; 6 | import java.text.DateFormat; 7 | import java.util.Date; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | public class SyncBundles{ 11 | private static final Pattern matcher = Pattern.compile("\\\\\\n"); 12 | public static final char SEP = '\\'; 13 | 14 | public static void main(String[] args){ 15 | Properties info = new Properties(new File("bundleInfo.properties")); 16 | String sourceLocale = info.get("source"); 17 | String bundlesDir = info.get("bundlesDir").replace("/", File.separator); 18 | String modName = info.get("modName"); 19 | Properties loc = new Properties(); 20 | loc.read(info.getOrigin("targetLocales").replace("[", "").replace("]", "").replace(SEP + System.lineSeparator(), System.lineSeparator())); 21 | String[] locales = new String[loc.map.size()*2]; 22 | int index = 0; 23 | for(Pair entry: loc.map.values()){ 24 | locales[index] = entry.key.replace("en_US", ""); 25 | locales[index+1] = entry.value; 26 | index += 2; 27 | } 28 | 29 | File source = new File(bundlesDir, "bundle" + (sourceLocale.isBlank() ? "": "_" + sourceLocale) + ".properties"); 30 | Properties sourceBundle = new Properties(); 31 | sourceBundle.read(source); 32 | 33 | handleHeader(sourceBundle, modName, sourceLocale, args); 34 | 35 | sourceBundle.write(source); 36 | for(int i = 0; i < locales.length; i += 2){ 37 | String locale = locales[i], mark = locales[i + 1]; 38 | File file = new File(bundlesDir, "bundle" + (locale.isBlank() ? "": "_" + locale) + ".properties"); 39 | Properties bundle = new Properties(sourceBundle, mark); 40 | if(file.exists()) bundle.read(file); 41 | handleHeader(bundle, modName, locale, args); 42 | bundle.write(file); 43 | } 44 | } 45 | 46 | public static void handleHeader(Properties source, String name, String locTag, String... args){ 47 | source.put(name + ".mod.updateDate", DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.forLanguageTag(locTag.replace("_", "-"))).format(new Date()), 0); 48 | 49 | source.put(name + ".mod.version", args[0], 0); 50 | } 51 | 52 | public static class Properties{ 53 | ArrayList lines = new ArrayList<>(); 54 | LinkedHashMap map = new LinkedHashMap<>(); 55 | 56 | String mark; 57 | 58 | public Properties(){} 59 | 60 | public Properties(File file){ 61 | read(file); 62 | } 63 | 64 | public Properties(Properties source, String mark){ 65 | this.mark = mark; 66 | for(Line line: source.lines){ 67 | Line l; 68 | if(line instanceof Pair){ 69 | l = new Pair(line.string, ((Pair) line).key, mark + " " + ((Pair) line).value); 70 | map.put(((Pair) l).key, (Pair) l); 71 | } 72 | else l = new Line(line.string); 73 | lines.add(l); 74 | } 75 | } 76 | 77 | public void put(String lineStr, int line){ 78 | lines.add(line, new Line(lineStr)); 79 | } 80 | 81 | public void put(String key, String value, int line){ 82 | key = string2Unicode(key); 83 | String v = string2Unicode(value); 84 | 85 | Pair pair = map.computeIfAbsent(key, k -> { 86 | Pair r = new Pair(k + " = " + v, k, v); 87 | lines.add(line, r); 88 | 89 | return r; 90 | }); 91 | pair.value = v; 92 | } 93 | 94 | public String get(String key){ 95 | String str = map.containsKey(key)? map.get(key).value: null; 96 | StringBuilder result = new StringBuilder(); 97 | 98 | for(String res: matcher.split(str)){ 99 | result.append(res); 100 | } 101 | 102 | return result.toString(); 103 | } 104 | 105 | public String getOrigin(String key){ 106 | return map.containsKey(key)? map.get(key).value: null; 107 | } 108 | 109 | public void read(String content){ 110 | read(new BufferedReader(new StringReader(content))); 111 | } 112 | 113 | public void read(File file){ 114 | try{ 115 | read(new BufferedReader(new FileReader(file))); 116 | }catch(FileNotFoundException e){ 117 | e.printStackTrace(); 118 | } 119 | } 120 | 121 | public void read(BufferedReader reader){ 122 | try{ 123 | String line; 124 | boolean init = lines.isEmpty(); 125 | Pair last = null; 126 | 127 | while((line = reader.readLine()) != null){ 128 | if(last != null){ 129 | last.value = last.value + line; 130 | 131 | if(line.endsWith(String.valueOf(SEP))){ 132 | last.value += System.lineSeparator(); 133 | } 134 | else last = null; 135 | 136 | continue; 137 | } 138 | 139 | String[] strs = line.trim().split("=", 2); 140 | if(init){ 141 | if(!strs[0].startsWith("#") && strs.length == 2){ 142 | Pair p; 143 | lines.add(p = new Pair(line, strs[0].trim(), strs[1].trim())); 144 | map.put(p.key, p); 145 | if(strs[1].endsWith(String.valueOf(SEP))){ 146 | last = p; 147 | p.value = p.value + System.lineSeparator(); 148 | } 149 | } 150 | else{ 151 | lines.add(new Line(line)); 152 | } 153 | } 154 | else { 155 | if(strs.length != 2) 156 | continue; 157 | Pair pair = map.get(strs[0].trim()); 158 | if(pair != null && !strs[1].trim().startsWith(mark)){ 159 | pair.value = strs[1].trim(); 160 | if(strs[1].endsWith(String.valueOf(SEP))){ 161 | last = pair; 162 | pair.value += System.lineSeparator(); 163 | } 164 | } 165 | } 166 | } 167 | }catch(IOException e){ 168 | throw new RuntimeException(e); 169 | } 170 | } 171 | 172 | public void write(File file) { 173 | try { 174 | BufferedWriter writer = new BufferedWriter(new FileWriter(file, StandardCharsets.ISO_8859_1, false)); 175 | for (Line line: lines) { 176 | writer.write(line.toString()); 177 | writer.newLine(); 178 | writer.flush(); 179 | } 180 | } catch (IOException e) { 181 | throw new RuntimeException(e); 182 | } 183 | } 184 | } 185 | 186 | public static class Line{ 187 | String string; 188 | 189 | public Line(String line){ 190 | this.string = line; 191 | } 192 | 193 | @Override 194 | public String toString(){ 195 | return string; 196 | } 197 | } 198 | 199 | public static class Pair extends Line{ 200 | String key; 201 | String value; 202 | 203 | public Pair(String line, String key, String value){ 204 | super(line); 205 | this.key = key; 206 | this.value = value; 207 | } 208 | 209 | @Override 210 | public String toString(){ 211 | return key + " = " + value; 212 | } 213 | } 214 | 215 | private static String string2Unicode(String string) { 216 | StringBuilder unicode = new StringBuilder(); 217 | for (int i = 0; i < string.length(); i++) { 218 | // 取出每一个字符 219 | char c = string.charAt(i); 220 | if (c<0x20 || c>0x7E) { 221 | // 转换为unicode 222 | String tmp = Integer.toHexString(c).toUpperCase(); 223 | if (tmp.length() == 4) { 224 | unicode.append("\\u").append(tmp); 225 | } else if (tmp.length() == 3){ 226 | unicode.append("\\u0").append(tmp); 227 | } else if (tmp.length() == 2){ 228 | unicode.append("\\u00").append(tmp); 229 | } else { 230 | unicode.append("\\u000").append(tmp); 231 | } 232 | } else { 233 | unicode.append(c); 234 | } 235 | } 236 | return unicode.toString(); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /assets/bundles/bundle.properties: -------------------------------------------------------------------------------- 1 | he.mod.version = beta-0.6 2 | he.mod.updateDate = 2025 Jun 5 3 | 4 | settings.helium = Helium Config 5 | settings.category.basic = Basic Settings 6 | settings.category.keyBind = Key Bindings 7 | settings.category.debug = Debug Settings 8 | 9 | settings.basic.backBlur = Background Blur 10 | settings.item.enableBlur = Enable background blur 11 | settings.item.blurScl = Blur level 12 | settings.item.blurSpace = Blur strength 13 | 14 | settings.basic.entityInfo = In-game Entity Information 15 | settings.item.enableEntityInfoDisplay = Enable entity info display 16 | settings.item.enableHealthBarDisplay = Enable health bar 17 | settings.item.enableUnitStatusDisplay = Enable status effect display 18 | settings.item.enableRangeDisplay = Enable entity range display 19 | settings.item.showAttackRange = Show attack range 20 | settings.item.showHealRange = Show heal range 21 | settings.item.showOverdriveRange = Show overdrive range 22 | settings.item.entityInfoScale = Entity info showing scale 23 | settings.item.entityInfoAlpha = Entity info showing alpha 24 | 25 | settings.basic.placement = Placement Panel 26 | settings.item.enableBetterPlacement = Enable better placement panel 27 | 28 | settings.basic.modsDialog = Mods Manager 29 | settings.item.enableBetterModsDialog = Enable better mods dialog 30 | 31 | settings.debug.logging = Log Information 32 | settings.item.loadingInfo = Output loading info 33 | 34 | settings.keyBind.placement = Placement Panel 35 | settings.item.placementFoldHotKey = Fold/Unfold placement panel 36 | settings.item.switchFastPageHotKey = Switch inventory page 37 | 38 | settings.keyBind.entityInfo = In-game Entity Information 39 | settings.item.entityInfoHotKey = Entity info setting hotkey 40 | 41 | config.showAttackRange = Show attack range 42 | config.showHealRange = Show heal range 43 | config.showOverdriveRange = Show overdrive range 44 | 45 | dialog.mods.enabled = Enabled Mods 46 | dialog.mods.disabled = Disabled Mods 47 | dialog.mods.menu = Menu 48 | dialog.mods.upgradeAll = All Upgrades 49 | dialog.mods.checkUpdate = Check Update 50 | dialog.mods.checkFailed = Get failed 51 | dialog.mods.checkUpdateFailed = Check update failed 52 | dialog.mods.downloadFailed = Download failed 53 | dialog.mods.checkUpdating = Checking update 54 | dialog.mods.updateValid = An update available for this mod\nLatest version:{0}. 55 | dialog.mods.updateValidS = A valid update was found. 56 | dialog.mods.modStatCorrect = This mod was correctly loaded. 57 | dialog.mods.modStatError = This mod is invalid. 58 | dialog.mods.isLatest = Current is the latest version. 59 | dialog.mods.exportPack = Export ModPack 60 | dialog.mods.refresh = Refresh 61 | dialog.mods.importFav = Import Favorites 62 | dialog.mods.inputFavText = Input favorites text\uFF1A 63 | dialog.mods.exportFav = Export Favorites 64 | dialog.mods.favoritesText = Export favorites text\uFF1A 65 | dialog.mods.hideInvalid = Hide invalid mods 66 | dialog.mods.author = Author: {0} 67 | dialog.mods.version = Version\uFF1A{0} 68 | dialog.mods.jarMod = This mod contains Java program. 69 | dialog.mods.jsMod = This mod contains JavaScript program. 70 | dialog.mods.hostMod = This mod will work on the server. 71 | dialog.mods.deprecated = This mod adapt game version was deprecated.\n[lightgray]missing "minGameVersion:{0}". 72 | dialog.mods.unsupported = Unsupported game version, your game was deprecated. 73 | dialog.mods.libMissing = Some dependence of this mod was missing. 74 | dialog.mods.libIncomplete = Some dependence of this mod was load failed. 75 | dialog.mods.libCircleDepending = There is some circular dependence in this mod dependence. 76 | dialog.mods.error = An error occurred with this mod, please report to the mod developer. 77 | dialog.mods.blackListed = This mod was blocklisted. 78 | dialog.mods.description = Description 79 | dialog.mods.rawText = Raw Text 80 | dialog.mods.contents = Mod Contents 81 | dialog.mods.shouldRelaunch = Should relaunch game 82 | dialog.mods.relaunch = Should relaunch game to apply modifies. 83 | dialog.mods.confirmDeleteHe = Delete Helium mod must relaunch game now! 84 | dialog.mods.deleteMod = Delete Mod 85 | dialog.mods.updateMod = Update Mod 86 | dialog.mods.downloadMod = Download Mod 87 | dialog.mods.importMod = Import Mod 88 | dialog.mods.otherHandle = Other Handles 89 | dialog.mods.reinstall = (Reinstall) 90 | dialog.mods.inputGithubLink = Input github repository link: 91 | dialog.mods.parsing = Parsing Link 92 | dialog.mods.parseFailed = Parse failed, check link 93 | dialog.mods.downloadComplete = Download Complete! 94 | dialog.mods.downloading = Downloading {0} 95 | dialog.mods.noLink = No Github link 96 | dialog.mods.exportLink = Export mod link 97 | dialog.mods.githubLink = The Github link of this mod\uFF1A 98 | dialog.mods.noGithubRepo = This mod was not open sourced on github 99 | dialog.mods.noDownloadLink = Download link was not found. 100 | dialog.mods.installed = This mod has been installed 101 | dialog.mods.newVersion = New version\uFF01 102 | dialog.mods.favorites = Favorites 103 | dialog.mods.noFavorites = No favorite mod yet 104 | dialog.mods.mods = Mod List 105 | dialog.mods.openFolderFailed = Cannot Open Folder 106 | dialog.mods.cantOpenOnAndroid = The file path cannot be opened on an Android device\nYou can copy the following path to open it in the file manager: 107 | 108 | tools.tip.ctrlEntityInfo = Select entity and show details 109 | tools.tip.configEntityInfo = Entity info display settings 110 | 111 | infos.relaunchEnsure = Some configure change must be restart game to take effect 112 | infos.confirmResetConfig = Are you sure to relaunch game? 113 | infos.enabledTeams = Enabled Teams 114 | infos.entityDetails = Details 115 | infos.entityRange = Range 116 | infos.healthDisplay = Health 117 | infos.statusDisplay = Status 118 | infos.enableAll = Enable All 119 | infos.disableAll = Disable All 120 | infos.allSetting = All Settings 121 | infos.copied = Link has been copied to clipboard. 122 | infos.wip = WIP 123 | infos.notImplementYet = Not Implement Yet 124 | infos.exception = Exception! 125 | infos.error = Error! 126 | 127 | misc.later = Later relaunch 128 | misc.exitGame = Exit game 129 | misc.recDefault = Restore default 130 | misc.open = Open 131 | misc.close = Close 132 | misc.copy = Copy 133 | misc.sequence = Forward Sort 134 | misc.reverse = Reverse Sort 135 | misc.download = Download 136 | misc.complete = Complete 137 | misc.save = Save 138 | -------------------------------------------------------------------------------- /assets/bundles/bundle_id_ID.properties: -------------------------------------------------------------------------------- 1 | he.mod.version = beta-0.6 2 | he.mod.updateDate = 7 Mei 2025 3 | 4 | settings.helium = Konfigurasi Helium 5 | settings.category.basic = Pengaturan Dasar 6 | settings.category.keyBind = Pengaturan Tombol 7 | settings.category.debug = Pengaturan Debug 8 | 9 | settings.basic.backBlur = Latar Belakang Buram 10 | settings.item.enableBlur = Nyalakan Latar Belakang Buram 11 | settings.item.blurScl = Tingkat keburaman 12 | settings.item.blurSpace = Intensitas keburaman 13 | 14 | settings.basic.entityInfo = Informasi Entitas Dalam Permainan 15 | settings.item.enableEntityInfoDisplay = Nyalakan info entitas 16 | settings.item.enableHealthBarDisplay = Nyalakan bar darah 17 | settings.item.enableUnitStatusDisplay = Nyalakan penampil status unit 18 | settings.item.enableRangeDisplay = Nyalakan penampil jarak entitas 19 | settings.item.showAttackRange = Tampilkan jarak serangan 20 | settings.item.showHealRange = Tampilkan jarak penyembuhan 21 | settings.item.showOverdriveRange = Tampilkan jarak pemercepat 22 | settings.item.entityInfoScale = Info entitas menunjukan skala 23 | settings.item.entityInfoAlpha = Info entitas menunjukan alfa(opasitas) 24 | 25 | settings.basic.placement = Panel Penempatan 26 | settings.item.enableBetterPlacement = Nyalakan panel penempatan yang lebih baik 27 | 28 | settings.basic.modsDialog = Manajer Mod 29 | settings.item.enableBetterModsDialog = Nyalakan dialog mod yang lebih baik 30 | 31 | settings.debug.logging = Informasi Log 32 | settings.item.loadingInfo = Keluaran info memuat 33 | 34 | settings.keyBind.placement = Panel Penempatan 35 | settings.item.placementFoldHotKey = Lipat/Buka lipat panel penempatan 36 | settings.item.switchFastPageHotKey = Tukar lembar inventaris 37 | 38 | settings.keyBind.entityInfo = Informasi Entitas Dalam Permainan 39 | settings.item.entityInfoHotKey = Pengaturan kunci info entitas 40 | 41 | config.showAttackRange = Tmplkn\njarak\nsrangn 42 | config.showHealRange = Tmplkn jrk pnymbhan 43 | config.showOverdriveRange = Tmplkn jrk pmrcpat 44 | 45 | dialog.mods.enabled = Mod Diaktifkan 46 | dialog.mods.disabled = Mod Dinonaktifkan 47 | dialog.mods.menu = Menu 48 | dialog.mods.upgradeAll = Semua Pembaruan 49 | dialog.mods.checkUpdate = Periksa Pembaruan 50 | dialog.mods.checkFailed = Gagal mendapat 51 | dialog.mods.checkUpdateFailed = Periksa pembaruan gagal 52 | dialog.mods.downloadFailed = Gagal mengunduh 53 | dialog.mods.checkUpdating = Mengeperiksa Pembaruan 54 | dialog.mods.updateValid = Pembaruan tersedia untuk mod ini\nVersi terakhir:{0}. 55 | dialog.mods.updateValidS = Pembaruan yang benar telah ditemukan. 56 | dialog.mods.modStatCorrect = Mod ini telah dimuat dengan tepat. 57 | dialog.mods.modStatError = Mod ini tidak sah. 58 | dialog.mods.isLatest = Saat ini adalah versi terakhir. 59 | dialog.mods.exportPack = Ekspor ModPack (Paket Mod) 60 | dialog.mods.refresh = Muat Ulang 61 | dialog.mods.importFav = Impor Favorit 62 | dialog.mods.exportFav = Ekspor Favorit 63 | dialog.mods.favoritesText = Ekspor teks favorit\uFF1A 64 | dialog.mods.hideInvalid = Sembunyikan mod cacat. 65 | dialog.mods.author = Author: {0} 66 | dialog.mods.version = Versi\uFF1A{0} 67 | dialog.mods.jarMod = Mod ini berisi program Java. 68 | dialog.mods.jsMod = Mod ini berisi program JavaScript. 69 | dialog.mods.hostMod = Mod in akan bekerja di dalam server. 70 | dialog.mods.deprecated = Versi game mod yang disesuaikan telah usang.\n[lightgray]"minGameVersion:{0}" hilang. 71 | dialog.mods.unsupported = Versi game tidak terdukung, permainanmu telah usang. 72 | dialog.mods.libMissing = Beberapa dependensi dari mod ini telah hilang. 73 | dialog.mods.libIncomplete = Beberapa dependensi dari mod ini gagal memuat. 74 | dialog.mods.libCircleDepending = Terdapat beberapa dependensi yang keliru pada dependensi mod ini. 75 | dialog.mods.error = Error terjadi pada mod ini, mohon laporkan ke pengembang mod. 76 | dialog.mods.blackListed = Mod ini telah terdaftar hitamkan. 77 | dialog.mods.description = Deskripsi 78 | dialog.mods.rawText = Teks Mentah 79 | dialog.mods.contents = Konten Mod 80 | dialog.mods.shouldRelaunch = Harus menjalankan ulang permainan 81 | dialog.mods.relaunch = Harus menjalankan ulang permainan untuk menerapkan modifikasi. 82 | dialog.mods.confirmDeleteHe = Harus menjalankan ulang permainan jika Hapus mod Helium sekarang! 83 | dialog.mods.deleteMod = Hapus Mod 84 | dialog.mods.updateMod = Pembarui Mod 85 | dialog.mods.downloadMod = Unduh Mod 86 | dialog.mods.importMod = Impor Mod 87 | dialog.mods.otherHandle = Pemegang Lainnya 88 | dialog.mods.reinstall = (Install Ulang) 89 | dialog.mods.inputGithubLink = Masukan tautan repositori Github: 90 | dialog.mods.parsing = Mengurai Tautan 91 | dialog.mods.parseFailed = Gagal mengurai, periksa tautan 92 | dialog.mods.downloadComplete = Unduh Selesai! 93 | dialog.mods.downloading = Mengunduh {0} 94 | dialog.mods.noLink = Tidak ada tautan Github 95 | dialog.mods.exportLink = Ekspor tautan mod 96 | dialog.mods.githubLink = Tautan Github dari mod ini\uFF1A 97 | dialog.mods.noGithubRepo = Mod ini tidak sumber terbuka di Github 98 | dialog.mods.noDownloadLink = Link unduhan tidak ditemukan. 99 | dialog.mods.installed = Mod ini telah terinstal 100 | dialog.mods.newVersion = Versi terbaru\uFF01 101 | dialog.mods.favorites = Favorit 102 | dialog.mods.noFavorites = Belum ada mod favorit barusan 103 | dialog.mods.mods = Daftar Mod 104 | dialog.mods.openFolderFailed = Tidak dapat membuka folder 105 | dialog.mods.cantOpenOnAndroid = Jalur file tidak dapat dibuka di Android\nKau dapat menyalinkan jalur dan membukanya di penelusur file: 106 | 107 | tools.tip.ctrlEntityInfo = Pilih entitas untuk menampilkan detil 108 | tools.tip.configEntityInfo = Pengaturan tampilan info entitas 109 | 110 | infos.relaunchEnsure = Beberapa perubahan konfigurasi harus diterapkan dengan menjalankan ulang permainan. 111 | infos.confirmResetConfig = Kau yakin untuk menjalankan ulang permainan? 112 | infos.enabledTeams = Tim Dinyalakan 113 | infos.entityDetails = Detil 114 | infos.entityRange = Jarak 115 | infos.healthDisplay = Darah 116 | infos.statusDisplay = Status 117 | infos.enableAll = Aktif\nSemua 118 | infos.disableAll = Nonaktif\nSemua 119 | infos.allSetting = Semua Pengaturan 120 | infos.copied = Tautan telah disalin ke papan klip 121 | infos.wip = WIP 122 | infos.notImplementYet = Belum diimplementasikan 123 | infos.exception = Kesalahan! 124 | infos.error = Error! 125 | 126 | misc.later = Luncurkan ulang nanti 127 | misc.exitGame = Keluar permainan 128 | misc.recDefault = Atur Ulang ke Bawaan 129 | misc.open = Buka 130 | misc.close = Tutup 131 | misc.copy = Salin 132 | misc.sequence = Urut Kedepan 133 | misc.reverse = Urut Terbalik 134 | misc.download = Unduh 135 | misc.complete = Selesai 136 | misc.save = Simpan 137 | -------------------------------------------------------------------------------- /assets/bundles/bundle_zh_CN.properties: -------------------------------------------------------------------------------- 1 | he.mod.version = beta-0.6 2 | he.mod.updateDate = 2025\u5E746\u67085\u65E5 3 | 4 | settings.helium = \u6C26\u914D\u7F6E 5 | settings.category.basic = \u57FA\u7840\u8BBE\u7F6E 6 | settings.category.keyBind = \u952E\u4F4D\u7ED1\u5B9A 7 | settings.category.debug = \u8C03\u8BD5\u8BBE\u7F6E 8 | 9 | settings.basic.backBlur = \u80CC\u666F\u6A21\u7CCA 10 | settings.item.enableBlur = \u542F\u7528\u80CC\u666F\u6A21\u7CCA 11 | settings.item.blurScl = \u6A21\u7CCA\u7EA7\u522B 12 | settings.item.blurSpace = \u6A21\u7CCA\u5F3A\u5EA6 13 | 14 | settings.basic.entityInfo = \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668 15 | settings.item.enableEntityInfoDisplay = \u542F\u7528\u5B9E\u4F53\u4FE1\u606F\u663E\u793A 16 | settings.item.enableHealthBarDisplay = \u542F\u7528\u751F\u547D\u503C\u663E\u793A\u9762\u677F 17 | settings.item.enableUnitStatusDisplay = \u542F\u7528\u5355\u4F4D\u72B6\u6001\u663E\u793A 18 | settings.item.enableRangeDisplay = \u542F\u7528\u8303\u56F4\u663E\u793A 19 | settings.item.showAttackRange = \u663E\u793A\u653B\u51FB\u8303\u56F4 20 | settings.item.showHealRange = \u663E\u793A\u4FEE\u590D\u8303\u56F4 21 | settings.item.showOverdriveRange = \u663E\u793A\u8D85\u901F\u6295\u5F71\u8303\u56F4 22 | settings.item.entityInfoScale = \u4FE1\u606F\u663E\u793A\u7F29\u653E 23 | settings.item.entityInfoAlpha = \u4FE1\u606F\u6307\u793A\u5668\u900F\u660E\u5EA6 24 | 25 | settings.basic.placement = \u653E\u7F6E\u9762\u677F 26 | settings.item.enableBetterPlacement = \u542F\u7528\u66F4\u597D\u7684\u653E\u7F6E\u9762\u677F 27 | 28 | settings.basic.modsDialog = \u6A21\u7EC4\u7BA1\u7406\u5668 29 | settings.item.enableBetterModsDialog = \u542F\u7528\u66F4\u597D\u7684\u6A21\u7EC4\u7BA1\u7406\u5668 30 | 31 | settings.debug.logging = \u65E5\u5FD7\u4FE1\u606F 32 | settings.item.loadingInfo = \u8F93\u51FA\u52A0\u8F7D\u4FE1\u606F 33 | 34 | settings.keyBind.placement = \u653E\u7F6E\u9762\u677F 35 | settings.item.placementFoldHotKey = \u6298\u53E0/\u5C55\u5F00\u653E\u7F6E\u9762\u677F 36 | settings.item.switchFastPageHotKey = \u5FEB\u901F\u7269\u54C1\u680F\u7FFB\u9875 37 | 38 | settings.keyBind.entityInfo = \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668 39 | settings.item.entityInfoHotKey = \u4FE1\u606F\u663E\u793A\u63A7\u5236\u952E 40 | 41 | config.showAttackRange = \u663E\u793A\u653B\u51FB\u8303\u56F4 42 | config.showHealRange = \u663E\u793A\u4FEE\u590D\u8303\u56F4 43 | config.showOverdriveRange = \u663E\u793A\u8D85\u901F\u6295\u5F71\u8303\u56F4 44 | 45 | dialog.mods.enabled = \u5DF2\u542F\u7528 46 | dialog.mods.disabled = \u5DF2\u7981\u7528 47 | dialog.mods.menu = \u83DC\u5355 48 | dialog.mods.upgradeAll = \u5168\u90E8\u66F4\u65B0 49 | dialog.mods.checkUpdate = \u68C0\u67E5\u66F4\u65B0 50 | dialog.mods.checkFailed = \u83B7\u53D6\u5931\u8D25 51 | dialog.mods.checkUpdateFailed = \u68C0\u67E5\u66F4\u65B0\u5931\u8D25 52 | dialog.mods.downloadFailed = \u4E0B\u8F7D\u5931\u8D25 53 | dialog.mods.checkUpdating = \u6B63\u5728\u68C0\u67E5\u66F4\u65B0 54 | dialog.mods.updateValid = \u6B64mod\u6709\u53EF\u7528\u7684\u66F4\u65B0\n\u6700\u65B0\u7248\u672C\uFF1A{0} 55 | dialog.mods.updateValidS = \u53D1\u73B0\u53EF\u7528\u7684\u66F4\u65B0 56 | dialog.mods.modStatCorrect = mod\u5DF2\u6B63\u5E38\u88C5\u8F7D 57 | dialog.mods.modStatError = \u6B64mod\u4E0D\u53EF\u7528 58 | dialog.mods.isLatest = \u5DF2\u662F\u6700\u65B0\u7248\u672C 59 | dialog.mods.exportPack = \u5BFC\u51FA\u6574\u5408\u5305 60 | dialog.mods.refresh = \u5237\u65B0 61 | dialog.mods.importFav = \u5BFC\u5165\u6536\u85CF\u5939 62 | dialog.mods.inputFavText = \u5728\u4E0B\u65B9\u8F93\u5165\u6536\u85CF\u5939\u6587\u672C\uFF1A 63 | dialog.mods.exportFav = \u5BFC\u51FA\u6536\u85CF\u5939 64 | dialog.mods.favoritesText = \u5BFC\u51FA\u6536\u85CF\u5939\u6587\u672C\uFF1A 65 | dialog.mods.hideInvalid = \u9690\u85CF\u4E0D\u53EF\u7528\u7684mod 66 | dialog.mods.author = \u4F5C\u8005\uFF1A{0} 67 | dialog.mods.version = \u7248\u672C\uFF1A{0} 68 | dialog.mods.jarMod = \u6B64mod\u5305\u542Bjava\u7A0B\u5E8F 69 | dialog.mods.jsMod = \u6B64mod\u5305\u542BJavaScript\u7A0B\u5E8F 70 | dialog.mods.hostMod = \u6B64mod\u4F1A\u5728\u670D\u52A1\u5668\u4E3B\u673A\u4E0A\u5DE5\u4F5C 71 | dialog.mods.deprecated = \u6B64mod\u9002\u914D\u7684\u6E38\u620F\u7248\u672C\u5DF2\u8FC7\u65F6\n[lightgray]\u7F3A\u5C11"minGameVersion:{0}" 72 | dialog.mods.unsupported = \u4E0D\u517C\u5BB9\u8FC7\u65F6\u7684\u6E38\u620F\u7248\u672C\u7248\u672C\uFF0C\u8BF7\u66F4\u65B0\u4F60\u7684\u6E38\u620F 73 | dialog.mods.libMissing = \u7F3A\u5C11\u6B64mod\u7684\u4F9D\u8D56mod 74 | dialog.mods.libIncomplete = \u6B64mod\u7684\u4F9D\u8D56\u9879\u5B58\u5728\u9519\u8BEF 75 | dialog.mods.libCircleDepending = \u6B64mod\u5B58\u5728\u4F9D\u8D56\u5FAA\u73AF\uFF0C\u8BF7\u62A5\u544Amod\u5F00\u53D1\u8005 76 | dialog.mods.error = \u6B64mod\u53D1\u751F\u9519\u8BEF\uFF0C\u8BF7\u62A5\u544Amod\u5F00\u53D1\u8005 77 | dialog.mods.blackListed = \u6B64mod\u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u5355 78 | dialog.mods.description = mod\u63CF\u8FF0 79 | dialog.mods.rawText = \u539F\u59CB\u6587\u672C 80 | dialog.mods.contents = mod\u5185\u5BB9 81 | dialog.mods.shouldRelaunch = \u9700\u8981\u91CD\u542F 82 | dialog.mods.relaunch = \u9700\u8981\u91CD\u542F\u6E38\u620F\u624D\u80FD\u5E94\u7528\u8FD9\u4E9B\u53D8\u66F4 83 | dialog.mods.confirmDeleteHe = \u5220\u9664\u6C26mod\u9700\u8981\u7ACB\u5373\u91CD\u542F\u6E38\u620F\uFF0C\u786E\u5B9A\u5417\uFF1F 84 | dialog.mods.deleteMod = \u5220\u9664mod 85 | dialog.mods.updateMod = \u66F4\u65B0mod 86 | dialog.mods.downloadMod = \u4E0B\u8F7Dmod 87 | dialog.mods.importMod = \u5BFC\u5165mod 88 | dialog.mods.otherHandle = \u5176\u4ED6\u64CD\u4F5C 89 | dialog.mods.reinstall = \uFF08\u91CD\u65B0\u5B89\u88C5\uFF09 90 | dialog.mods.inputGithubLink = \uF308 \u8F93\u5165mod\u7684Github\u4ED3\u5E93\u94FE\u63A5 91 | dialog.mods.parsing = \u6B63\u5728\u89E3\u6790\u94FE\u63A5 92 | dialog.mods.parseFailed = \u89E3\u6790\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u94FE\u63A5\u662F\u5426\u6B63\u786E 93 | dialog.mods.downloadComplete = \u4E0B\u8F7D\u5B8C\u6210 94 | dialog.mods.downloading = \u6B63\u5728\u4E0B\u8F7D {0} 95 | dialog.mods.noLink = \u672A\u627E\u5230\u94FE\u63A5 96 | dialog.mods.exportLink = \u5BFC\u51FA\u94FE\u63A5 97 | dialog.mods.githubLink = \u6B64mod\u7684github\u94FE\u63A5\uFF1A 98 | dialog.mods.noGithubRepo = \u6B64mod\u6CA1\u6709\u5F00\u6E90\u5230github 99 | dialog.mods.noDownloadLink = \u6CA1\u6709\u627E\u5230\u4E0B\u8F7D\u94FE\u63A5 100 | dialog.mods.installed = \u6B64mod\u5DF2\u5B89\u88C5 101 | dialog.mods.newVersion = \u65B0\u7248\u672C\uFF01 102 | dialog.mods.favorites = \u6536\u85CF\u5939 103 | dialog.mods.noFavorites = \u6CA1\u6709\u6536\u85CF\u4EFB\u4F55mod 104 | dialog.mods.mods = mod\u5217\u8868 105 | dialog.mods.openFolderFailed = \u65E0\u6CD5\u6253\u5F00\u76EE\u5F55 106 | dialog.mods.cantOpenOnAndroid = \u5728Android\u8BBE\u5907\u4E0A\u65E0\u6CD5\u6253\u5F00\u6587\u4EF6\u8DEF\u5F84\n\u4F60\u53EF\u4EE5\u590D\u5236\u5982\u4E0B\u8DEF\u5F84\u624B\u52A8\u5728\u6587\u4EF6\u7BA1\u7406\u5668\u4E2D\u6253\u5F00\uFF1A 107 | 108 | tools.tip.ctrlEntityInfo = \u9009\u62E9\u5E76\u663E\u793A\u5B9E\u4F53\u8BE6\u60C5 109 | tools.tip.configEntityInfo = \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668\u8BBE\u7F6E 110 | 111 | infos.relaunchEnsure = \u4E00\u4E9B\u914D\u7F6E\u9879\u7684\u53D8\u66F4\u9700\u8981\u91CD\u542F\u751F\u6548 112 | infos.confirmResetConfig = \u786E\u5B9A\u8981\u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E\u5417\uFF1F 113 | infos.enabledTeams = \u542F\u7528\u7684\u961F\u4F0D 114 | infos.entityDetails = \u8BE6\u7EC6\u4FE1\u606F 115 | infos.entityRange = \u8303\u56F4\u663E\u793A\u5668 116 | infos.healthDisplay = \u751F\u547D\u663E\u793A\u5668 117 | infos.statusDisplay = \u72B6\u6001\u6307\u793A\u5668 118 | infos.enableAll = \u542F\u7528\u5168\u90E8 119 | infos.disableAll = \u7981\u7528\u5168\u90E8 120 | infos.allSetting = \u5168\u90E8\u8BBE\u7F6E 121 | infos.copied = \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F 122 | infos.wip = WIP 123 | infos.notImplementYet = \u6682\u672A\u5B9E\u73B0 124 | infos.exception = \u53D1\u751F\u5F02\u5E38\uFF01 125 | infos.error = \u53D1\u751F\u9519\u8BEF\uFF01 126 | 127 | misc.later = \u7A0D\u540E\u91CD\u542F 128 | misc.exitGame = \u9000\u51FA\u6E38\u620F 129 | misc.recDefault = \u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E 130 | misc.open = \u6253\u5F00 131 | misc.close = \u5173\u95ED 132 | misc.copy = \u590D\u5236 133 | misc.sequence = \u987A\u5E8F 134 | misc.reverse = \u9006\u5E8F 135 | misc.download = \u4E0B\u8F7D 136 | misc.complete = \u5B8C\u6210 137 | misc.save = \u4FDD\u5B58 138 | -------------------------------------------------------------------------------- /assets/bundles/bundle_zh_TW.properties: -------------------------------------------------------------------------------- 1 | he.mod.version = beta-0.6 2 | he.mod.updateDate = 2025\u5E746\u67085\u65E5 3 | 4 | settings.helium = (\u5F85\u7FFB\u8B6F) \u6C26\u914D\u7F6E 5 | settings.category.basic = (\u5F85\u7FFB\u8B6F) \u57FA\u7840\u8BBE\u7F6E 6 | settings.category.keyBind = (\u5F85\u7FFB\u8B6F) \u952E\u4F4D\u7ED1\u5B9A 7 | settings.category.debug = (\u5F85\u7FFB\u8B6F) \u8C03\u8BD5\u8BBE\u7F6E 8 | 9 | settings.basic.backBlur = (\u5F85\u7FFB\u8B6F) \u80CC\u666F\u6A21\u7CCA 10 | settings.item.enableBlur = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u80CC\u666F\u6A21\u7CCA 11 | settings.item.blurScl = (\u5F85\u7FFB\u8B6F) \u6A21\u7CCA\u7EA7\u522B 12 | settings.item.blurSpace = (\u5F85\u7FFB\u8B6F) \u6A21\u7CCA\u5F3A\u5EA6 13 | 14 | settings.basic.entityInfo = (\u5F85\u7FFB\u8B6F) \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668 15 | settings.item.enableEntityInfoDisplay = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u5B9E\u4F53\u4FE1\u606F\u663E\u793A 16 | settings.item.enableHealthBarDisplay = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u751F\u547D\u503C\u663E\u793A\u9762\u677F 17 | settings.item.enableUnitStatusDisplay = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u5355\u4F4D\u72B6\u6001\u663E\u793A 18 | settings.item.enableRangeDisplay = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u8303\u56F4\u663E\u793A 19 | settings.item.showAttackRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u653B\u51FB\u8303\u56F4 20 | settings.item.showHealRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u4FEE\u590D\u8303\u56F4 21 | settings.item.showOverdriveRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u8D85\u901F\u6295\u5F71\u8303\u56F4 22 | settings.item.entityInfoScale = (\u5F85\u7FFB\u8B6F) \u4FE1\u606F\u663E\u793A\u7F29\u653E 23 | settings.item.entityInfoAlpha = (\u5F85\u7FFB\u8B6F) \u4FE1\u606F\u6307\u793A\u5668\u900F\u660E\u5EA6 24 | 25 | settings.basic.placement = (\u5F85\u7FFB\u8B6F) \u653E\u7F6E\u9762\u677F 26 | settings.item.enableBetterPlacement = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u66F4\u597D\u7684\u653E\u7F6E\u9762\u677F 27 | 28 | settings.basic.modsDialog = (\u5F85\u7FFB\u8B6F) \u6A21\u7EC4\u7BA1\u7406\u5668 29 | settings.item.enableBetterModsDialog = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u66F4\u597D\u7684\u6A21\u7EC4\u7BA1\u7406\u5668 30 | 31 | settings.debug.logging = (\u5F85\u7FFB\u8B6F) \u65E5\u5FD7\u4FE1\u606F 32 | settings.item.loadingInfo = (\u5F85\u7FFB\u8B6F) \u8F93\u51FA\u52A0\u8F7D\u4FE1\u606F 33 | 34 | settings.keyBind.placement = (\u5F85\u7FFB\u8B6F) \u653E\u7F6E\u9762\u677F 35 | settings.item.placementFoldHotKey = (\u5F85\u7FFB\u8B6F) \u6298\u53E0/\u5C55\u5F00\u653E\u7F6E\u9762\u677F 36 | settings.item.switchFastPageHotKey = (\u5F85\u7FFB\u8B6F) \u5FEB\u901F\u7269\u54C1\u680F\u7FFB\u9875 37 | 38 | settings.keyBind.entityInfo = (\u5F85\u7FFB\u8B6F) \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668 39 | settings.item.entityInfoHotKey = (\u5F85\u7FFB\u8B6F) \u4FE1\u606F\u663E\u793A\u63A7\u5236\u952E 40 | 41 | config.showAttackRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u653B\u51FB\u8303\u56F4 42 | config.showHealRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u4FEE\u590D\u8303\u56F4 43 | config.showOverdriveRange = (\u5F85\u7FFB\u8B6F) \u663E\u793A\u8D85\u901F\u6295\u5F71\u8303\u56F4 44 | 45 | dialog.mods.enabled = (\u5F85\u7FFB\u8B6F) \u5DF2\u542F\u7528 46 | dialog.mods.disabled = (\u5F85\u7FFB\u8B6F) \u5DF2\u7981\u7528 47 | dialog.mods.menu = (\u5F85\u7FFB\u8B6F) \u83DC\u5355 48 | dialog.mods.upgradeAll = (\u5F85\u7FFB\u8B6F) \u5168\u90E8\u66F4\u65B0 49 | dialog.mods.checkUpdate = (\u5F85\u7FFB\u8B6F) \u68C0\u67E5\u66F4\u65B0 50 | dialog.mods.checkFailed = (\u5F85\u7FFB\u8B6F) \u83B7\u53D6\u5931\u8D25 51 | dialog.mods.checkUpdateFailed = (\u5F85\u7FFB\u8B6F) \u68C0\u67E5\u66F4\u65B0\u5931\u8D25 52 | dialog.mods.downloadFailed = (\u5F85\u7FFB\u8B6F) \u4E0B\u8F7D\u5931\u8D25 53 | dialog.mods.checkUpdating = (\u5F85\u7FFB\u8B6F) \u6B63\u5728\u68C0\u67E5\u66F4\u65B0 54 | dialog.mods.updateValid = (\u5F85\u7FFB\u8B6F) \u6B64mod\u6709\u53EF\u7528\u7684\u66F4\u65B0\n\u6700\u65B0\u7248\u672C\uFF1A{0} 55 | dialog.mods.updateValidS = (\u5F85\u7FFB\u8B6F) \u53D1\u73B0\u53EF\u7528\u7684\u66F4\u65B0 56 | dialog.mods.modStatCorrect = (\u5F85\u7FFB\u8B6F) mod\u5DF2\u6B63\u5E38\u88C5\u8F7D 57 | dialog.mods.modStatError = (\u5F85\u7FFB\u8B6F) \u6B64mod\u4E0D\u53EF\u7528 58 | dialog.mods.isLatest = (\u5F85\u7FFB\u8B6F) \u5DF2\u662F\u6700\u65B0\u7248\u672C 59 | dialog.mods.exportPack = (\u5F85\u7FFB\u8B6F) \u5BFC\u51FA\u6574\u5408\u5305 60 | dialog.mods.refresh = (\u5F85\u7FFB\u8B6F) \u5237\u65B0 61 | dialog.mods.importFav = (\u5F85\u7FFB\u8B6F) \u5BFC\u5165\u6536\u85CF\u5939 62 | dialog.mods.inputFavText = (\u5F85\u7FFB\u8B6F) \u5728\u4E0B\u65B9\u8F93\u5165\u6536\u85CF\u5939\u6587\u672C\uFF1A 63 | dialog.mods.exportFav = (\u5F85\u7FFB\u8B6F) \u5BFC\u51FA\u6536\u85CF\u5939 64 | dialog.mods.favoritesText = (\u5F85\u7FFB\u8B6F) \u5BFC\u51FA\u6536\u85CF\u5939\u6587\u672C\uFF1A 65 | dialog.mods.hideInvalid = (\u5F85\u7FFB\u8B6F) \u9690\u85CF\u4E0D\u53EF\u7528\u7684mod 66 | dialog.mods.author = (\u5F85\u7FFB\u8B6F) \u4F5C\u8005\uFF1A{0} 67 | dialog.mods.version = (\u5F85\u7FFB\u8B6F) \u7248\u672C\uFF1A{0} 68 | dialog.mods.jarMod = (\u5F85\u7FFB\u8B6F) \u6B64mod\u5305\u542Bjava\u7A0B\u5E8F 69 | dialog.mods.jsMod = (\u5F85\u7FFB\u8B6F) \u6B64mod\u5305\u542BJavaScript\u7A0B\u5E8F 70 | dialog.mods.hostMod = (\u5F85\u7FFB\u8B6F) \u6B64mod\u4F1A\u5728\u670D\u52A1\u5668\u4E3B\u673A\u4E0A\u5DE5\u4F5C 71 | dialog.mods.deprecated = (\u5F85\u7FFB\u8B6F) \u6B64mod\u9002\u914D\u7684\u6E38\u620F\u7248\u672C\u5DF2\u8FC7\u65F6\n[lightgray]\u7F3A\u5C11"minGameVersion:{0}" 72 | dialog.mods.unsupported = (\u5F85\u7FFB\u8B6F) \u4E0D\u517C\u5BB9\u8FC7\u65F6\u7684\u6E38\u620F\u7248\u672C\u7248\u672C\uFF0C\u8BF7\u66F4\u65B0\u4F60\u7684\u6E38\u620F 73 | dialog.mods.libMissing = (\u5F85\u7FFB\u8B6F) \u7F3A\u5C11\u6B64mod\u7684\u4F9D\u8D56mod 74 | dialog.mods.libIncomplete = (\u5F85\u7FFB\u8B6F) \u6B64mod\u7684\u4F9D\u8D56\u9879\u5B58\u5728\u9519\u8BEF 75 | dialog.mods.libCircleDepending = (\u5F85\u7FFB\u8B6F) \u6B64mod\u5B58\u5728\u4F9D\u8D56\u5FAA\u73AF\uFF0C\u8BF7\u62A5\u544Amod\u5F00\u53D1\u8005 76 | dialog.mods.error = (\u5F85\u7FFB\u8B6F) \u6B64mod\u53D1\u751F\u9519\u8BEF\uFF0C\u8BF7\u62A5\u544Amod\u5F00\u53D1\u8005 77 | dialog.mods.blackListed = (\u5F85\u7FFB\u8B6F) \u6B64mod\u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u5355 78 | dialog.mods.description = (\u5F85\u7FFB\u8B6F) mod\u63CF\u8FF0 79 | dialog.mods.rawText = (\u5F85\u7FFB\u8B6F) \u539F\u59CB\u6587\u672C 80 | dialog.mods.contents = (\u5F85\u7FFB\u8B6F) mod\u5185\u5BB9 81 | dialog.mods.shouldRelaunch = (\u5F85\u7FFB\u8B6F) \u9700\u8981\u91CD\u542F 82 | dialog.mods.relaunch = (\u5F85\u7FFB\u8B6F) \u9700\u8981\u91CD\u542F\u6E38\u620F\u624D\u80FD\u5E94\u7528\u8FD9\u4E9B\u53D8\u66F4 83 | dialog.mods.confirmDeleteHe = (\u5F85\u7FFB\u8B6F) \u5220\u9664\u6C26mod\u9700\u8981\u7ACB\u5373\u91CD\u542F\u6E38\u620F\uFF0C\u786E\u5B9A\u5417\uFF1F 84 | dialog.mods.deleteMod = (\u5F85\u7FFB\u8B6F) \u5220\u9664mod 85 | dialog.mods.updateMod = (\u5F85\u7FFB\u8B6F) \u66F4\u65B0mod 86 | dialog.mods.downloadMod = (\u5F85\u7FFB\u8B6F) \u4E0B\u8F7Dmod 87 | dialog.mods.importMod = (\u5F85\u7FFB\u8B6F) \u5BFC\u5165mod 88 | dialog.mods.otherHandle = (\u5F85\u7FFB\u8B6F) \u5176\u4ED6\u64CD\u4F5C 89 | dialog.mods.reinstall = (\u5F85\u7FFB\u8B6F) \uFF08\u91CD\u65B0\u5B89\u88C5\uFF09 90 | dialog.mods.inputGithubLink = (\u5F85\u7FFB\u8B6F) \uF308 \u8F93\u5165mod\u7684Github\u4ED3\u5E93\u94FE\u63A5 91 | dialog.mods.parsing = (\u5F85\u7FFB\u8B6F) \u6B63\u5728\u89E3\u6790\u94FE\u63A5 92 | dialog.mods.parseFailed = (\u5F85\u7FFB\u8B6F) \u89E3\u6790\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u94FE\u63A5\u662F\u5426\u6B63\u786E 93 | dialog.mods.downloadComplete = (\u5F85\u7FFB\u8B6F) \u4E0B\u8F7D\u5B8C\u6210 94 | dialog.mods.downloading = (\u5F85\u7FFB\u8B6F) \u6B63\u5728\u4E0B\u8F7D {0} 95 | dialog.mods.noLink = (\u5F85\u7FFB\u8B6F) \u672A\u627E\u5230\u94FE\u63A5 96 | dialog.mods.exportLink = (\u5F85\u7FFB\u8B6F) \u5BFC\u51FA\u94FE\u63A5 97 | dialog.mods.githubLink = (\u5F85\u7FFB\u8B6F) \u6B64mod\u7684github\u94FE\u63A5\uFF1A 98 | dialog.mods.noGithubRepo = (\u5F85\u7FFB\u8B6F) \u6B64mod\u6CA1\u6709\u5F00\u6E90\u5230github 99 | dialog.mods.noDownloadLink = (\u5F85\u7FFB\u8B6F) \u6CA1\u6709\u627E\u5230\u4E0B\u8F7D\u94FE\u63A5 100 | dialog.mods.installed = (\u5F85\u7FFB\u8B6F) \u6B64mod\u5DF2\u5B89\u88C5 101 | dialog.mods.newVersion = (\u5F85\u7FFB\u8B6F) \u65B0\u7248\u672C\uFF01 102 | dialog.mods.favorites = (\u5F85\u7FFB\u8B6F) \u6536\u85CF\u5939 103 | dialog.mods.noFavorites = (\u5F85\u7FFB\u8B6F) \u6CA1\u6709\u6536\u85CF\u4EFB\u4F55mod 104 | dialog.mods.mods = (\u5F85\u7FFB\u8B6F) mod\u5217\u8868 105 | dialog.mods.openFolderFailed = (\u5F85\u7FFB\u8B6F) \u65E0\u6CD5\u6253\u5F00\u76EE\u5F55 106 | dialog.mods.cantOpenOnAndroid = (\u5F85\u7FFB\u8B6F) \u5728Android\u8BBE\u5907\u4E0A\u65E0\u6CD5\u6253\u5F00\u6587\u4EF6\u8DEF\u5F84\n\u4F60\u53EF\u4EE5\u590D\u5236\u5982\u4E0B\u8DEF\u5F84\u624B\u52A8\u5728\u6587\u4EF6\u7BA1\u7406\u5668\u4E2D\u6253\u5F00\uFF1A 107 | 108 | tools.tip.ctrlEntityInfo = (\u5F85\u7FFB\u8B6F) \u9009\u62E9\u5E76\u663E\u793A\u5B9E\u4F53\u8BE6\u60C5 109 | tools.tip.configEntityInfo = (\u5F85\u7FFB\u8B6F) \u5B9E\u4F53\u4FE1\u606F\u663E\u793A\u5668\u8BBE\u7F6E 110 | 111 | infos.relaunchEnsure = (\u5F85\u7FFB\u8B6F) \u4E00\u4E9B\u914D\u7F6E\u9879\u7684\u53D8\u66F4\u9700\u8981\u91CD\u542F\u751F\u6548 112 | infos.confirmResetConfig = (\u5F85\u7FFB\u8B6F) \u786E\u5B9A\u8981\u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E\u5417\uFF1F 113 | infos.enabledTeams = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u7684\u961F\u4F0D 114 | infos.entityDetails = (\u5F85\u7FFB\u8B6F) \u8BE6\u7EC6\u4FE1\u606F 115 | infos.entityRange = (\u5F85\u7FFB\u8B6F) \u8303\u56F4\u663E\u793A\u5668 116 | infos.healthDisplay = (\u5F85\u7FFB\u8B6F) \u751F\u547D\u663E\u793A\u5668 117 | infos.statusDisplay = (\u5F85\u7FFB\u8B6F) \u72B6\u6001\u6307\u793A\u5668 118 | infos.enableAll = (\u5F85\u7FFB\u8B6F) \u542F\u7528\u5168\u90E8 119 | infos.disableAll = (\u5F85\u7FFB\u8B6F) \u7981\u7528\u5168\u90E8 120 | infos.allSetting = (\u5F85\u7FFB\u8B6F) \u5168\u90E8\u8BBE\u7F6E 121 | infos.copied = (\u5F85\u7FFB\u8B6F) \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F 122 | infos.wip = (\u5F85\u7FFB\u8B6F) WIP 123 | infos.notImplementYet = (\u5F85\u7FFB\u8B6F) \u6682\u672A\u5B9E\u73B0 124 | infos.exception = (\u5F85\u7FFB\u8B6F) \u53D1\u751F\u5F02\u5E38\uFF01 125 | infos.error = (\u5F85\u7FFB\u8B6F) \u53D1\u751F\u9519\u8BEF\uFF01 126 | 127 | misc.later = (\u5F85\u7FFB\u8B6F) \u7A0D\u540E\u91CD\u542F 128 | misc.exitGame = (\u5F85\u7FFB\u8B6F) \u9000\u51FA\u6E38\u620F 129 | misc.recDefault = (\u5F85\u7FFB\u8B6F) \u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E 130 | misc.open = (\u5F85\u7FFB\u8B6F) \u6253\u5F00 131 | misc.close = (\u5F85\u7FFB\u8B6F) \u5173\u95ED 132 | misc.copy = (\u5F85\u7FFB\u8B6F) \u590D\u5236 133 | misc.sequence = (\u5F85\u7FFB\u8B6F) \u987A\u5E8F 134 | misc.reverse = (\u5F85\u7FFB\u8B6F) \u9006\u5E8F 135 | misc.download = (\u5F85\u7FFB\u8B6F) \u4E0B\u8F7D 136 | misc.complete = (\u5F85\u7FFB\u8B6F) \u5B8C\u6210 137 | misc.save = (\u5F85\u7FFB\u8B6F) \u4FDD\u5B58 138 | -------------------------------------------------------------------------------- /assets/config/mod_config.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "configVersion": 3, 3 | 4 | //=====Basic/基础设置=====// 5 | 6 | //启用UI模糊视效 7 | "enableBlur": true, 8 | //模糊级别 9 | "blurScl": 4, 10 | //模糊强度 11 | "blurSpace": 1.25, 12 | 13 | //启用实体信息显示器 14 | "enableEntityInfoDisplay": true, 15 | //信息显示缩放 16 | "entityInfoScale": 1.0, 17 | //信息指示器透明度 18 | "entityInfoAlpha": 1.0, 19 | //启用生命值面板 20 | "enableHealthBarDisplay": true, 21 | //启用状态指示器 22 | "enableUnitStatusDisplay": true, 23 | //启用范围指示器 24 | "enableRangeDisplay": true, 25 | //显示实体攻击范围 26 | "showAttackRange": true, 27 | //显示修复范围 28 | "showHealRange": true, 29 | //显示超速投影作用范围 30 | "showOverdriveRange": true, 31 | 32 | //启用更好的放置面板 33 | "enableBetterPlacement": true, 34 | 35 | //启用更好的mod设置页面 36 | "enableBetterModsDialog": true, 37 | 38 | //=====KeyBinds/键位绑定=====// 39 | 40 | //实体信息控制热键 41 | "entityInfoHotKey": "altLeft", 42 | 43 | //快速物品栏翻页 44 | "switchFastPageHotKey": "tab", 45 | //展开/收起方块选择面板 46 | "placementFoldHotKey": "q", 47 | 48 | //=====Debug/调试设置=====// 49 | 50 | //启用加载信息 51 | "loadInfo": true, 52 | } 53 | -------------------------------------------------------------------------------- /assets/shaders/dist_base.frag: -------------------------------------------------------------------------------- 1 | 2 | uniform sampler2D u_texture; 3 | 4 | varying vec2 v_texCoords; 5 | 6 | void main() { 7 | gl_FragColor.rgb = texture2D(u_texture, v_texCoords).rgb; 8 | gl_FragColor.a = 1.0; 9 | } 10 | -------------------------------------------------------------------------------- /assets/shaders/entity_range.frag: -------------------------------------------------------------------------------- 1 | #define HIGHP 2 | 3 | uniform sampler2D u_texture; 4 | 5 | uniform vec2 u_campos; 6 | uniform vec2 u_resolution; 7 | uniform float u_time; 8 | uniform float u_stroke; 9 | uniform float u_alpha; 10 | 11 | varying vec2 v_texCoords; 12 | 13 | const float threshold = 0.01; 14 | const float PI = 3.14159265359; 15 | 16 | void main() { 17 | vec2 v = u_stroke*(1.0 / u_resolution); 18 | 19 | vec4 base = texture2D(u_texture, v_texCoords); 20 | vec2 worldCoord = vec2(v_texCoords.x * u_resolution.x + u_campos.x, v_texCoords.y * u_resolution.y + u_campos.y); 21 | 22 | //float m = min(min(min( 23 | // texture2D(u_texture, v_texCoords + vec2(1.0, 0.0) * v).a, 24 | // texture2D(u_texture, v_texCoords + vec2(0.0, 1.0) * v).a), 25 | // texture2D(u_texture, v_texCoords + vec2(-1.0, 0.0) * v).a), 26 | // texture2D(u_texture, v_texCoords + vec2(0.0, -1.0) * v).a); 27 | 28 | float m = min(min(min(min(min(min(min( 29 | texture2D(u_texture, v_texCoords + vec2(1.0, 0.0) * v).a, 30 | texture2D(u_texture, v_texCoords + vec2(0.7071, 0.7071) * v).a), 31 | texture2D(u_texture, v_texCoords + vec2(0.0, 1.0) * v).a), 32 | texture2D(u_texture, v_texCoords + vec2(-0.7071, 0.7071) * v).a), 33 | texture2D(u_texture, v_texCoords + vec2(-1.0, 0.0) * v).a), 34 | texture2D(u_texture, v_texCoords + vec2(-0.7071, -0.7071) * v).a), 35 | texture2D(u_texture, v_texCoords + vec2(0.0, -1.0) * v).a), 36 | texture2D(u_texture, v_texCoords + vec2(0.7071, -0.7071) * v).a); 37 | 38 | float time = u_time*0.1; 39 | float a = 40 | sin((worldCoord.x + worldCoord.y)*0.0831 + time*0.0095) + 41 | sin((-worldCoord.x + worldCoord.y)*0.075 + time*0.056) + 42 | sin((worldCoord.x - worldCoord.y)*0.0546 + time*0.21) + 43 | sin((-worldCoord.x - worldCoord.y)*0.03432 + time*0.712); 44 | a = (a/4.0 + 1.0)/2.0; 45 | 46 | float s = length(base.rgb); 47 | 48 | if(base.a > threshold && m < threshold) { 49 | gl_FragColor = vec4(s <= threshold? vec3(1.0): base.rgb, a); 50 | } 51 | else if (base.a >= threshold) { 52 | vec4 c1 = vec4(base.rgb, base.a*u_alpha); 53 | vec4 c2 = vec4(vec3(1.0), u_alpha); 54 | gl_FragColor = s <= threshold? mix(c1, c2, a): c1; 55 | } 56 | else { 57 | gl_FragColor = vec4(0.0); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /assets/sprites/ui/background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/background.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/healthbar.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/healthbar.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/helium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/helium.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/java.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/javascript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/javascript.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/network-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/network-error.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/program.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/program.png -------------------------------------------------------------------------------- /assets/sprites/ui/icons/slots-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/icons/slots-back.png -------------------------------------------------------------------------------- /assets/sprites/ui/shieldbar.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/assets/sprites/ui/shieldbar.9.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | import java.io.FileOutputStream 4 | import java.io.InputStreamReader 5 | import java.io.StringReader 6 | import java.util.jar.JarEntry 7 | import java.util.jar.JarOutputStream 8 | 9 | val mindustryVersion = "v149" 10 | val arcVersion = "v149" 11 | 12 | val modOutputDir = properties["modOutputDir"] as? String 13 | 14 | val sdkRoot: String? = System.getenv("ANDROID_HOME") 15 | 16 | val buildDir = layout.buildDirectory.get() 17 | val projectName = project.name 18 | 19 | plugins { 20 | java 21 | kotlin("jvm") version "2.1.20" 22 | `maven-publish` 23 | } 24 | 25 | group = "com.github.EB-wilson" 26 | version = "beta-0.7" 27 | 28 | run { "java SyncBundles.java $version".execute() } 29 | 30 | java { 31 | sourceCompatibility = JavaVersion.VERSION_1_8 32 | targetCompatibility = JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlin { 36 | jvmToolchain(21) 37 | 38 | compilerOptions { 39 | jvmTarget.set(JvmTarget.JVM_1_8) 40 | } 41 | } 42 | 43 | publishing { 44 | publications { 45 | create("maven") { 46 | from(components["java"]) 47 | 48 | groupId = "com.github.EB-wilson" 49 | artifactId = "TooManyItems" 50 | version = "${project.version}" 51 | } 52 | } 53 | } 54 | 55 | repositories { 56 | mavenLocal() 57 | mavenCentral() 58 | maven ("https://maven.xpdustry.com/mindustry") 59 | maven ("https://www.jitpack.io") 60 | } 61 | 62 | dependencies { 63 | compileOnly("com.github.Anuken.Arc:arc-core:$arcVersion") 64 | compileOnly("com.github.Anuken.Mindustry:core:$mindustryVersion") 65 | 66 | implementation("com.github.EB-wilson.UniverseCore:markdown:2.3.1") 67 | 68 | implementation(kotlin("stdlib-jdk8")) 69 | } 70 | 71 | tasks { 72 | jar { 73 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 74 | archiveFileName = "${project.name}-desktop.jar" 75 | 76 | from(rootDir) { 77 | include("mod.hjson") 78 | include("icon.png") 79 | include("contributors.hjson") 80 | } 81 | 82 | from("assets/") { 83 | include("**") 84 | exclude("git") 85 | } 86 | 87 | from(configurations.getByName("runtimeClasspath").map { if (it.isDirectory) it else zipTree(it) }) 88 | } 89 | 90 | register("jarAndroid") { 91 | dependsOn("jar") 92 | 93 | doLast { 94 | try { 95 | if (sdkRoot == null) throw GradleException("No valid Android SDK found. Ensure that ANDROID_HOME is set to your Android SDK directory."); 96 | 97 | val platformRoot = File("$sdkRoot/platforms/").listFiles() 98 | ?.sorted() 99 | ?.reversed() 100 | ?.find { f -> File (f, "android.jar").exists() } 101 | 102 | if (platformRoot == null) throw GradleException("No android.jar found. Ensure that you have an Android platform installed.") 103 | 104 | //collect dependencies needed for desugaring 105 | val dependencies = ( 106 | configurations.compileClasspath.get().files + 107 | configurations.runtimeClasspath.get().files + 108 | setOf(File(platformRoot, "android.jar")) 109 | ).joinToString(" ") { "--classpath $it" } 110 | 111 | //dex and desugar files - this requires d8 in your PATH 112 | "d8 $dependencies --min-api 14 --output ${project.name}-android.jar ${project.name}-desktop.jar" 113 | .execute(File("$buildDir/libs")) 114 | } 115 | catch (e: Throwable) { 116 | if (e is Error){ 117 | println(e.message) 118 | return@doLast 119 | } 120 | 121 | println("[WARNING] d8 tool or platform tools was not found, if you was installed android SDK, please check your environment variable") 122 | 123 | delete( 124 | files("${buildDir}/libs/${project.name}-android.jar") 125 | ) 126 | 127 | val out = JarOutputStream(FileOutputStream("${buildDir}/libs/${project.name}-android.jar")) 128 | out.putNextEntry(JarEntry("non-androidMod.txt")) 129 | val reader = StringReader( 130 | "this mod is don't have classes.dex for android, please consider recompile with a SDK or run this mod on desktop only" 131 | ) 132 | 133 | var r = reader.read() 134 | while (r != -1) { 135 | out.write(r) 136 | out.flush() 137 | r = reader.read() 138 | } 139 | out.close() 140 | } 141 | } 142 | } 143 | 144 | register("deploy", Jar::class) { 145 | dependsOn("jarAndroid") 146 | archiveFileName = "${project.name}.jar" 147 | 148 | from ( 149 | zipTree("${buildDir}/libs/${project.name}-desktop.jar"), 150 | zipTree("${buildDir}/libs/${project.name}-android.jar") 151 | ) 152 | 153 | doLast { 154 | if (!modOutputDir.isNullOrEmpty()) { 155 | copy { 156 | into("$modOutputDir/") 157 | from("${buildDir}/libs/${project.name}.jar") 158 | } 159 | } 160 | } 161 | } 162 | 163 | register("deployDesktop", Jar::class) { 164 | dependsOn("jar") 165 | archiveFileName = "${project.name}.jar" 166 | 167 | from (zipTree("${buildDir}/libs/${project.name}-desktop.jar")) 168 | 169 | doLast { 170 | if (!modOutputDir.isNullOrEmpty()) { 171 | copy { 172 | into("$modOutputDir/") 173 | from("${buildDir}/libs/${project.name}.jar") 174 | } 175 | } 176 | } 177 | } 178 | 179 | register("debugMod", JavaExec::class) { 180 | dependsOn("classes") 181 | dependsOn("deployDesktop") 182 | 183 | mainClass = "-jar" 184 | args = listOf( 185 | project.properties["debugGamePath"] as? String?:"", 186 | "-debug" 187 | ) 188 | } 189 | } 190 | 191 | fun String.execute(path: File? = null, vararg args: Any?): Process{ 192 | val cmd = split(Regex("\\s+")) 193 | .toMutableList() 194 | .apply { addAll(args.map { it?.toString()?:"null" }) } 195 | .toTypedArray() 196 | val process = ProcessBuilder(*cmd) 197 | .directory(path?:rootDir) 198 | .redirectOutput(ProcessBuilder.Redirect.INHERIT) 199 | .redirectError(ProcessBuilder.Redirect.INHERIT) 200 | .start() 201 | 202 | if (process.waitFor() != 0) throw Error(InputStreamReader(process.errorStream).readText()) 203 | 204 | return process 205 | } 206 | 207 | class Error(str: String): RuntimeException(str) 208 | -------------------------------------------------------------------------------- /bundleInfo.properties: -------------------------------------------------------------------------------- 1 | bundlesDir = assets/bundles/ 2 | 3 | modName = he 4 | source = zh_CN 5 | 6 | targetLocales = [\ 7 | en_US = (wait to translate)\ 8 | zh_TW = (\u5F85\u7FFB\u8B6F)\ 9 | ru = (\u043D\u0443\u0436\u0435\u043D \u043F\u0435\u0440\u0435\u0432\u043E\u0434)\ 10 | ja = (\u7FFB\u8A33\u5F85\u3061)\ 11 | de_DE = (\u00DCbersetzung w\u00FCnschen)\ 12 | ] 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=--illegal-access=permit \ 2 | --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ 3 | --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ 4 | --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED \ 5 | --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \ 6 | --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 7 | --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ 8 | --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 9 | --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 10 | --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \ 11 | --add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ 12 | --add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ 13 | --add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED 14 | 15 | modOutputDir = 16 | debugGamePath = 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/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.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/icon.png -------------------------------------------------------------------------------- /ktstools/genReflectCstrWrap.kts: -------------------------------------------------------------------------------- 1 | val builder = StringBuilder() 2 | 3 | for(n in 0..20){ 4 | val typeArgs = StringBuilder() 5 | val typeArgsUse = StringBuilder() 6 | val types = StringBuilder() 7 | for (value in 1..n) { 8 | typeArgs.append("reified ").append("P").append(value).append(", ") 9 | typeArgsUse.append("P").append(value).append(", ") 10 | types.append("P").append(value).append("::class.java").append(", ") 11 | if (value%5 == 0) types.append("\n ") 12 | } 13 | 14 | val res = 15 | """ 16 | inline fun 0) typeArgs.substring(0, typeArgs.length - 2) else typeArgs}> accessConstructor$n() = 17 | ConstructorInvoker$n<${typeArgsUse}O>(O::class.java.getConstructor( 18 | ${if (n > 0) types.substring(0, types.length - if (n % 5 == 0) 5 else 2) else types} 19 | ).also { 20 | it.isAccessible = true 21 | })""" 22 | 23 | builder.append(res) 24 | } 25 | 26 | System.out.print(builder.toString()) -------------------------------------------------------------------------------- /ktstools/genReflectCstrs.kts: -------------------------------------------------------------------------------- 1 | 2 | val builder = StringBuilder() 3 | 4 | for(n in 0..22){ 5 | val typeArgs = StringBuilder() 6 | val argsDecl = StringBuilder() 7 | val args = StringBuilder() 8 | for (value in 1..n) { 9 | typeArgs.append("P").append(value).append(", ") 10 | argsDecl.append("p").append(value).append(": ").append("P").append(value).append(", ") 11 | args.append("p").append(value).append(", ") 12 | } 13 | 14 | val res = 15 | """ 16 | class ConstructorInvoker$n<${typeArgs}O>(private val constructor: Constructor) 17 | : (${if (n > 0) typeArgs.substring(0, typeArgs.length - 2) else typeArgs}) -> O { 18 | override fun invoke(${if (n > 0) argsDecl.substring(0, argsDecl.length - 2) else ""}) 19 | = constructor.newInstance(${if (n > 0) args.substring(0, args.length - 2) else ""}) 20 | }""" 21 | 22 | builder.append(res) 23 | } 24 | 25 | System.out.print(builder.toString()) -------------------------------------------------------------------------------- /ktstools/genReflectMetWrap.kts: -------------------------------------------------------------------------------- 1 | val builder = StringBuilder() 2 | 3 | for(n in 0..20){ 4 | val typeArgs = StringBuilder() 5 | val typeArgsUse = StringBuilder() 6 | val types = StringBuilder() 7 | for (value in 1..n) { 8 | typeArgs.append("reified ").append("P").append(value).append(", ") 9 | typeArgsUse.append("P").append(value).append(", ") 10 | types.append("P").append(value).append("::class.java").append(", ") 11 | if (value%5 == 0) types.append("\n ") 12 | } 13 | 14 | val res = 15 | """ 16 | inline fun accessMethod$n(name: String) = 17 | MethodInvoker$n(O::class.java.getDeclaredMethod( 18 | name, 19 | ${if (n > 0) types.substring(0, types.length - if (n % 5 == 0) 5 else 2) else types} 20 | ).also { 21 | checkReturnType(it, R::class.java) 22 | it.isAccessible = true 23 | })""" 24 | 25 | builder.append(res) 26 | } 27 | 28 | System.out.print(builder.toString()) -------------------------------------------------------------------------------- /ktstools/genReflectMethods.kts: -------------------------------------------------------------------------------- 1 | 2 | val builder = StringBuilder() 3 | 4 | for(n in 0..22){ 5 | val typeArgs = StringBuilder() 6 | val argsDecl = StringBuilder() 7 | val args = StringBuilder() 8 | for (value in 1..n) { 9 | typeArgs.append("P").append(value).append(", ") 10 | argsDecl.append("p").append(value).append(": ").append("P").append(value).append(", ") 11 | args.append("p").append(value).append(", ") 12 | } 13 | 14 | val res = 15 | """ 16 | class MethodInvoker$n(private val method: Method) 17 | : (O, ${if (n > 0) typeArgs.substring(0, typeArgs.length - 2) else typeArgs}) -> R { 18 | override fun invoke(self: O, ${if (n > 0) argsDecl.substring(0, argsDecl.length - 2) else ""}) 19 | = method.invoke(self, ${if (n > 0) args.substring(0, args.length - 2) else ""}) as R 20 | }""" 21 | 22 | builder.append(res) 23 | } 24 | 25 | System.out.print(builder.toString()) -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | { 2 | displayName: "Helium", 3 | name: "he", 4 | author: "EBwilson", 5 | main: "helium.Helium", 6 | subtitle: "A mod provide optimized ui", 7 | description: ''' 8 | A mod provide optimized ui and more utilities. 9 | Major features: 10 | - ***In-Game Entity Information Components***: Provides various UI elements for game entities including units and blocks 11 | - ***Health & Status Indicators***: Displays real-time status (health, shields) above units and entities. When shield stacks exceed max health, multi-layer shields with stack counters are shown. 12 | - ***Attack Range Indicators***: Units and turrets now visualize their attack ranges as translucent areas. Processed boundaries emphasize combined team range contours to prevent visual clutter, with subtle pulsating animations and chromatic borders. 13 | - ***Functional Block Effect Range Indicators***: Devices like Mend Projectors, Overdrive Projectors, and unit repair stations display effect ranges with distinctive colors. 14 | - ***Enhanced Block Placement Panel***: A refined block selection panel integrating a quick item bar and standardized tool buttons. 15 | - ***The better Mod Manager and Mod Browser***: Reworked the Mods Manager and Mods Browser, now they looks more aesthetically pleasing and functional 16 | ''', 17 | version: "beta-0.7", 18 | minGameVersion: 149, 19 | hidden: true 20 | } -------------------------------------------------------------------------------- /preview_imgs/en/attackRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/attackRange.png -------------------------------------------------------------------------------- /preview_imgs/en/blur-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/blur-1.png -------------------------------------------------------------------------------- /preview_imgs/en/blur-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/blur-2.png -------------------------------------------------------------------------------- /preview_imgs/en/configEntry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/configEntry.png -------------------------------------------------------------------------------- /preview_imgs/en/configurePane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/configurePane.png -------------------------------------------------------------------------------- /preview_imgs/en/control-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/control-button.png -------------------------------------------------------------------------------- /preview_imgs/en/effectRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/effectRange.png -------------------------------------------------------------------------------- /preview_imgs/en/modBrowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/modBrowser.png -------------------------------------------------------------------------------- /preview_imgs/en/modManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/modManager.png -------------------------------------------------------------------------------- /preview_imgs/en/placement-fold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/placement-fold.png -------------------------------------------------------------------------------- /preview_imgs/en/placement-unfold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/placement-unfold.png -------------------------------------------------------------------------------- /preview_imgs/en/quick-config-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/quick-config-entry.png -------------------------------------------------------------------------------- /preview_imgs/en/quick-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/quick-config.png -------------------------------------------------------------------------------- /preview_imgs/en/statusDisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/en/statusDisplay.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/attackRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/attackRange.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/blur-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/blur-1.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/blur-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/blur-2.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/configEntry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/configEntry.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/configurePane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/configurePane.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/control-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/control-button.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/effectRange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/effectRange.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/modBrowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/modBrowser.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/modManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/modManager.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/placement-fold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/placement-fold.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/placement-unfold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/placement-unfold.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/quick-config-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/quick-config-entry.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/quick-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/quick-config.png -------------------------------------------------------------------------------- /preview_imgs/zh_CN/statusDisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EB-wilson/Helium/20d6f03ceae4027b553ffe3f05237d60f553b310/preview_imgs/zh_CN/statusDisplay.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Helium" 2 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/HeConfig.kt: -------------------------------------------------------------------------------- 1 | package helium 2 | 3 | import arc.files.Fi 4 | import arc.input.KeyCode 5 | import arc.util.Log 6 | import arc.util.Threads 7 | import arc.util.serialization.Jval 8 | import java.io.IOException 9 | 10 | private typealias RArray = java.lang.reflect.Array 11 | 12 | class HeConfig(configDir: Fi, internalSource: Fi) { 13 | companion object { 14 | private var exec = Threads.unboundedExecutor("HTTP", 1) 15 | private val commentMatcher = Regex("(//.*)|(/\\*(.|\\s)+\\*/)") 16 | private val keyMatcher = Regex("\"?(\\w+)\"?\\s*:\\s*") 17 | private val jsonArrayMatcher = Regex("\\[(.|\\s)*]") 18 | private val jsonObjectMatcher = Regex("\\{(.|\\s)*\\}") 19 | private val jsonValueMatcher = Regex("(\".*\")|([\\w.]+)") 20 | 21 | private val configs = HeConfig::class.java.declaredFields 22 | .filter { it.getAnnotation(ConfigItem::class.java) != null } 23 | } 24 | 25 | private val configFile = configDir.child("mod_config.hjson") 26 | private val configBack = configDir.child("mod_config.hjson.bak") 27 | private val internalConfigFile = internalSource 28 | private val configVersion = Jval.read(internalConfigFile.reader()).getInt("configVersion", 0) 29 | 30 | @ConfigItem var loadInfo = true 31 | 32 | @ConfigItem var enableBlur = true 33 | @ConfigItem var blurScl = 4 34 | @ConfigItem var blurSpace = 1.25f 35 | 36 | @ConfigItem var enableEntityInfoDisplay = true 37 | @ConfigItem var enableHealthBarDisplay = true 38 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 39 | @ConfigItem var hideHealthText = false 40 | @ConfigItem var enableUnitStatusDisplay = true 41 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 42 | @ConfigItem var enableRangeDisplay = true 43 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 44 | @ConfigItem var showAttackRange = true 45 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 46 | @ConfigItem var showHealRange = true 47 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 48 | @ConfigItem var showOverdriveRange = true 49 | set(value){ field = value; He.entityInfo.displaySetupUpdated() } 50 | @ConfigItem var entityInfoHotKey = KeyCode.altLeft 51 | @ConfigItem var entityInfoScale = 1f 52 | @ConfigItem var entityInfoAlpha = 1f 53 | 54 | @ConfigItem var enableBetterPlacement = true 55 | @ConfigItem var switchFastPageHotKey = KeyCode.tab 56 | @ConfigItem var placementFoldHotKey = KeyCode.q 57 | 58 | @ConfigItem var enableBetterModsDialog = true 59 | 60 | fun load() { 61 | if (!configFile.exists()) { 62 | internalConfigFile.copyTo(configFile) 63 | Log.info("Configuration file is not exist, copying the default configuration") 64 | load(configFile) 65 | } else { 66 | if (!load(configFile)) { 67 | val backup = configBack 68 | configFile.copyTo(backup) 69 | internalConfigFile.copyTo(configFile) 70 | Log.info("default configuration file version updated, old config should be override(backup file for old file was created)") 71 | save() 72 | } 73 | } 74 | 75 | if (loadInfo) printConfig() 76 | } 77 | 78 | private fun printConfig() { 79 | val results = StringBuilder() 80 | 81 | configs.forEach { cfg -> 82 | results.append(" ") 83 | .append(cfg.name) 84 | .append(" = ") 85 | .append( 86 | cfg.get(this).let { 87 | when(it){ 88 | is Array<*> -> it.contentToString() 89 | is ByteArray -> it.contentToString() 90 | is ShortArray -> it.contentToString() 91 | is IntArray -> it.contentToString() 92 | is LongArray -> it.contentToString() 93 | is FloatArray -> it.contentToString() 94 | is DoubleArray -> it.contentToString() 95 | is CharArray -> it.contentToString() 96 | is BooleanArray -> it.contentToString() 97 | else -> it.toString() 98 | } 99 | } 100 | ) 101 | .append(";") 102 | .append(System.lineSeparator()) 103 | } 104 | 105 | Log.info("Mod config loaded! The config data:[${System.lineSeparator()}$results]") 106 | } 107 | 108 | private fun load(file: Fi): Boolean { 109 | val sb = StringBuilder() 110 | file.reader().use { reader -> 111 | val part = CharArray(8192) 112 | var n: Int 113 | while (reader.read(part, 0, part.size).also { n = it } != -1) { 114 | sb.appendRange(part, 0, n) 115 | } 116 | } 117 | 118 | val config = Jval.read(file.reader()) 119 | 120 | val old = config.get("configVersion").asInt() != configVersion 121 | 122 | configs.forEach { cfg -> 123 | if (!config.has(cfg.name)) return@forEach 124 | 125 | val temp = config.get(cfg.name).toString() 126 | cfg.set(this, warp(cfg.type, temp)) 127 | } 128 | 129 | return !old 130 | } 131 | 132 | fun saveAsync() { 133 | exec.submit { 134 | synchronized(this, ::save) 135 | } 136 | } 137 | 138 | fun save() { 139 | try { 140 | save(configFile) 141 | } catch (e: IOException) { 142 | Log.err(e.toString()) 143 | } 144 | } 145 | 146 | @Suppress("UNCHECKED_CAST") 147 | private fun save(file: Fi) { 148 | val tree = Jval.newObject() 149 | val map = tree.asObject() 150 | map.put("configVersion", Jval.valueOf(configVersion)) 151 | 152 | configs.forEach { cfg -> 153 | val key = cfg.name 154 | val obj = cfg.get(this) 155 | when { 156 | obj == null -> when { 157 | CharSequence::class.java.isAssignableFrom(cfg.type) -> map.put(key, Jval.valueOf("")) 158 | cfg.type.isArray -> map.put(key, Jval.newArray()) 159 | cfg.type.isEnum -> map.put(key, Jval.valueOf(firstEnum(cfg.type as Class>).name)) 160 | else -> throw RuntimeException("Unhandled null type") 161 | } 162 | else -> map.put(key, pack(obj)) 163 | } 164 | } 165 | 166 | val stringBuilder = StringBuilder() 167 | 168 | val fileContext = file.reader().readText() 169 | var lastStart = 0 170 | commentMatcher.findAll(fileContext).forEach { res -> 171 | val end = res.range.first 172 | val subString = fileContext.substring(lastStart, end) 173 | 174 | handleText(subString, stringBuilder, map) 175 | 176 | stringBuilder.append(res.value) 177 | 178 | lastStart = res.range.last + 1 179 | } 180 | if (lastStart < fileContext.length) { 181 | val subString = fileContext.substring(lastStart) 182 | handleText(subString, stringBuilder, map) 183 | } 184 | 185 | file.writeString(stringBuilder.toString()) 186 | } 187 | 188 | private fun handleText( 189 | subString: String, 190 | stringBuilder: StringBuilder, 191 | map: Jval.JsonMap, 192 | ) { 193 | var lastKeyEnd = 0 194 | keyMatcher.findAll(subString).forEach pair@{ key -> 195 | val keyName = key.groups[1]!! 196 | 197 | val perpend = subString.substring(lastKeyEnd, key.range.first) 198 | stringBuilder.append(perpend) 199 | 200 | stringBuilder.append(key.value) 201 | stringBuilder.append(map.get(keyName.value)) 202 | 203 | lastKeyEnd = key.range.last + 1 204 | } 205 | if (lastKeyEnd < subString.length) { 206 | stringBuilder.append( 207 | subString 208 | .substring(lastKeyEnd) 209 | .replace(jsonValueMatcher, "") 210 | .replace(jsonArrayMatcher, "") 211 | .replace(jsonObjectMatcher, "") 212 | ) 213 | } 214 | } 215 | 216 | private fun pack(value: Any): Jval { 217 | return when (value) { 218 | is Int -> Jval.valueOf(value) 219 | is Byte -> Jval.valueOf(value.toInt()) 220 | is Short -> Jval.valueOf(value.toInt()) 221 | is Boolean -> Jval.valueOf(value) 222 | is Long -> Jval.valueOf(value) 223 | is Char -> Jval.valueOf(value.code) 224 | is Float -> Jval.valueOf(value) 225 | is Double -> Jval.valueOf(value) 226 | is CharSequence -> Jval.valueOf(value.toString()) 227 | else -> when { 228 | value.javaClass.isArray -> packArray(value) 229 | value.javaClass.isEnum -> Jval.valueOf((value as Enum<*>).name) 230 | else -> throw RuntimeException("invalid type: ${value.javaClass}") 231 | } 232 | } 233 | } 234 | 235 | private fun packArray(array: Any): Jval { 236 | if (!array.javaClass.isArray) throw RuntimeException("given object was not an array") 237 | 238 | val len = RArray.getLength(array) 239 | val res = Jval.newArray() 240 | val arr = res.asArray() 241 | (0 until len).forEach { i -> 242 | arr.add(pack(RArray.get(array, i))) 243 | } 244 | return res 245 | } 246 | 247 | @Suppress("UNCHECKED_CAST") 248 | private fun warp(type: Class, value: String): T { 249 | return when (type) { 250 | Int::class.java -> value.toInt() as T 251 | Byte::class.java -> value.toByte() as T 252 | Short::class.java -> value.toShort() as T 253 | Boolean::class.java -> value.toBoolean() as T 254 | Long::class.java -> value.toLong() as T 255 | Char::class.java -> value[0] as T 256 | Float::class.java -> value.toFloat() as T 257 | Double::class.java -> value.toDouble() as T 258 | else -> when { 259 | CharSequence::class.java.isAssignableFrom(type) -> value as T 260 | type.isArray -> toArray(type, value) 261 | type.isEnum -> (type as Class>).enumConstants.find { it.name == value } as T 262 | else -> throw RuntimeException("invalid type: $type") 263 | } 264 | } 265 | } 266 | 267 | @Suppress("UNCHECKED_CAST") 268 | private fun toArray(type: Class, value: String): T { 269 | if (!type.isArray) throw RuntimeException("class $type was not an array") 270 | val a = Jval.read(value).asArray() 271 | val eleType = type.componentType 272 | val res = RArray.newInstance(eleType, a.size) 273 | (0 until a.size).forEach { i -> 274 | RArray.set(res, i, warp(eleType, a[i].toString())) 275 | } 276 | return res as T 277 | } 278 | 279 | private fun > firstEnum(type: Class): T { 280 | if (!type.isEnum) throw RuntimeException("class $type was not an enum") 281 | 282 | return type.enumConstants[0] 283 | } 284 | 285 | fun reset() { 286 | configFile.copyTo(configBack) 287 | configFile.delete() 288 | Log.info("[INFO][${He.MOD_NAME}] mod config has been reset, old config file saved to file named \"mod_config.hjson.bak\"") 289 | load() 290 | } 291 | } 292 | 293 | @Retention(AnnotationRetention.RUNTIME) 294 | @Target(AnnotationTarget.FIELD) 295 | annotation class ConfigItem 296 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/Helium.kt: -------------------------------------------------------------------------------- 1 | package helium 2 | 3 | import arc.Core 4 | import arc.files.Fi 5 | import arc.graphics.g2d.TextureRegion 6 | import arc.scene.style.Drawable 7 | import helium.graphics.ScreenSampler 8 | import mindustry.mod.Mod 9 | 10 | class Helium : Mod() { 11 | init { 12 | ScreenSampler.resetMark() 13 | } 14 | 15 | override fun init() { 16 | ScreenSampler.setup() 17 | 18 | He.init() 19 | } 20 | 21 | companion object { 22 | fun getInternalFile(path: String): Fi { 23 | return He.modFile.child(path) 24 | } 25 | 26 | fun getDrawable(spriteName: String): T = 27 | Core.atlas.getDrawable("${He.INTERNAL_NAME}-$spriteName") 28 | fun getAtlas(spriteName: String): TextureRegion = 29 | Core.atlas.find("${He.INTERNAL_NAME}-$spriteName") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/BaseClipDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.scene.style.BaseDrawable 4 | 5 | abstract class BaseClipDrawable(): ClipDrawable { 6 | @Suppress("LeakingThis") 7 | constructor(other: ClipDrawable): this(){ 8 | if (other is BaseDrawable) name = other.name 9 | leftWidth = other.leftWidth 10 | rightWidth = other.rightWidth 11 | topHeight = other.topHeight 12 | bottomHeight = other.bottomHeight 13 | minWidth = other.minWidth 14 | minHeight = other.minHeight 15 | } 16 | 17 | var name: String? = null 18 | override var leftWidth: Float = 0f 19 | override var rightWidth: Float = 0f 20 | override var topHeight: Float = 0f 21 | override var bottomHeight: Float = 0f 22 | override var minWidth: Float = 0f 23 | override var minHeight: Float = 0f 24 | 25 | override fun toString(): String { 26 | if (name == null) return javaClass.toString() 27 | return name!! 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/BaseStripDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | abstract class BaseStripDrawable: StripDrawable { 4 | override var leftOff: Float = 0f 5 | override var rightOff: Float = 0f 6 | override var outerWidth: Float = 0f 7 | override var innerWidth: Float = 0f 8 | override var minOffset: Float = 0f 9 | override var minWidth: Float = 0f 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/Blur.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.Core 4 | import arc.graphics.Blending 5 | import arc.graphics.Color 6 | import arc.graphics.gl.FrameBuffer 7 | import arc.graphics.gl.Shader 8 | import arc.util.Log 9 | import kotlin.math.abs 10 | 11 | val DEf_A: FloatArray = floatArrayOf( 12 | 0.008697324f, 0.035994977f, 0.10936101f, 13 | 0.21296589f, 0.26596153f, 0.21296589f, 14 | 0.10936101f, 0.035994977f, 0.008697324f, 15 | ) 16 | val DEf_B: FloatArray = floatArrayOf( 17 | 0.044408645f, 0.07799442f, 0.11599662f, 18 | 0.16730806f, 0.1885769f, 0.16730806f, 19 | 0.11599662f, 0.07799442f, 0.044408645f, 20 | ) 21 | val DEf_C: FloatArray = floatArrayOf( 22 | 0.0045418483f, 0.053999867f, 0.24198672f, 23 | 0.39894313f, 24 | 0.24198672f, 0.053999867f, 0.0045418483f, 25 | ) 26 | val DEf_D: FloatArray = floatArrayOf( 27 | 0.02454185f, 0.06399987f, 0.2519867f, 28 | 0.3189431f, 29 | 0.2519867f, 0.06399987f, 0.02454185f, 30 | ) 31 | val DEf_E: FloatArray = floatArrayOf( 32 | 0.01961571f, 0.20542552f, 33 | 0.55991757f, 34 | 0.20542552f, 0.01961571f, 35 | ) 36 | val DEf_F: FloatArray = floatArrayOf( 37 | 0.07027027f, 0.31621623f, 38 | 0.22702703f, 39 | 0.31621623f, 0.07027027f, 40 | ) 41 | val DEf_G: FloatArray = floatArrayOf( 42 | 0.20798193f, 43 | 0.68403614f, 44 | 0.20798193f, 45 | ) 46 | val DEf_H: FloatArray = floatArrayOf( 47 | 0.25617367f, 48 | 0.4876527f, 49 | 0.25617367f, 50 | ) 51 | 52 | private const val vertTemplate = 53 | """ 54 | attribute vec4 a_position; 55 | attribute vec2 a_texCoord0; 56 | 57 | uniform vec2 u_dir; 58 | uniform vec2 u_size; 59 | 60 | varying vec2 v_texCoords; 61 | 62 | %varying% 63 | 64 | void main(){ 65 | vec2 len = u_dir/u_size; 66 | 67 | v_texCoords = a_texCoord0; 68 | %assignVar% 69 | gl_Position = a_position; 70 | } 71 | """ 72 | private const val fragmentTemplate = 73 | """ 74 | uniform lowp sampler2D u_texture0; 75 | uniform lowp sampler2D u_texture1; 76 | 77 | uniform lowp float u_def_alpha; 78 | 79 | varying vec2 v_texCoords; 80 | 81 | %varying% 82 | 83 | void main(){ 84 | vec4 blur = texture2D(u_texture0, v_texCoords); 85 | vec3 color = texture2D(u_texture1, v_texCoords).rgb; 86 | 87 | if(blur.a > 0.0){ 88 | vec3 blurColor = 89 | %convolution% 90 | 91 | gl_FragColor.rgb = mix(color, blurColor, blur.a); 92 | gl_FragColor.a = 1.0; 93 | } 94 | else{ 95 | gl_FragColor.rgb = color; 96 | gl_FragColor.a = u_def_alpha; 97 | } 98 | } 99 | """ 100 | 101 | class Blur(vararg convolutions: Float = DEf_F) { 102 | var blurShader: Shader 103 | var buffer: FrameBuffer 104 | var pingpong: FrameBuffer 105 | 106 | var capturing: Boolean = false 107 | 108 | var blurScl: Int = 4 109 | var blurSpace: Float = 2.16f 110 | 111 | init { 112 | blurShader = genShader(*convolutions) 113 | 114 | buffer = FrameBuffer() 115 | pingpong = FrameBuffer() 116 | 117 | blurShader.bind() 118 | blurShader.setUniformi("u_texture0", 0) 119 | blurShader.setUniformi("u_texture1", 1) 120 | } 121 | 122 | private fun genShader(vararg convolutions: Float): Shader { 123 | require(convolutions.size%2 == 1) { "convolution numbers length must be odd number!" } 124 | 125 | val convLen = convolutions.size 126 | 127 | val varyings = StringBuilder() 128 | val assignVar = StringBuilder() 129 | val convolution = StringBuilder() 130 | 131 | var c = 0 132 | val half = convLen/2 133 | for (v in convolutions) { 134 | varyings.append("varying vec2 v_texCoords") 135 | .append(c) 136 | .append(";") 137 | .append("\n") 138 | 139 | assignVar.append("v_texCoords") 140 | .append(c) 141 | .append(" = ") 142 | .append("a_texCoord0") 143 | if (c - half != 0) { 144 | assignVar.append(if (c - half > 0) "+" else "-") 145 | .append(abs((c.toFloat() - half).toDouble()).toFloat()) 146 | .append("*len") 147 | } 148 | assignVar.append(";") 149 | .append("\n") 150 | .append(" ") 151 | 152 | if (c > 0) convolution.append(" + ") 153 | convolution.append(v) 154 | .append("*texture2D(u_texture1, v_texCoords") 155 | .append(c) 156 | .append(")") 157 | .append(".rgb") 158 | .append("\n") 159 | 160 | c++ 161 | } 162 | convolution.append(";") 163 | 164 | val vertexShader = vertTemplate 165 | .replace("%varying%", varyings.toString()) 166 | .replace("%assignVar%", assignVar.toString()) 167 | val fragmentShader = fragmentTemplate 168 | .replace("%varying%", varyings.toString()) 169 | .replace("%convolution%", convolution.toString()) 170 | 171 | Log.info(vertexShader) 172 | Log.info(fragmentShader) 173 | 174 | return Shader(vertexShader, fragmentShader) 175 | } 176 | 177 | fun resize(width: Int, height: Int) { 178 | val w = width/blurScl 179 | val h = height/blurScl 180 | 181 | buffer.resize(w, h) 182 | pingpong.resize(w, h) 183 | 184 | blurShader.bind() 185 | blurShader.setUniformf("u_size", w.toFloat(), h.toFloat()) 186 | } 187 | 188 | fun capture() { 189 | if (!capturing) { 190 | buffer.begin(Color.clear) 191 | 192 | capturing = true 193 | } 194 | } 195 | 196 | fun render() { 197 | if (!capturing) return 198 | capturing = false 199 | buffer.end() 200 | 201 | Blending.disabled.apply() 202 | 203 | blurShader.bind() 204 | blurShader.apply() 205 | blurShader.setUniformf("u_dir", blurSpace, 0f) 206 | blurShader.setUniformf("u_def_alpha", 1f) 207 | buffer.texture.bind(0) 208 | 209 | pingpong.begin() 210 | ScreenSampler.blit(blurShader, 1) 211 | pingpong.end() 212 | 213 | blurShader.setUniformf("u_dir", 0f, blurSpace) 214 | blurShader.setUniformf("u_def_alpha", 0f) 215 | pingpong.texture.bind(1) 216 | 217 | Blending.normal.apply() 218 | buffer.blit(blurShader) 219 | } 220 | 221 | fun directDraw(draw: Runnable) { 222 | resize(Core.graphics.width, Core.graphics.height) 223 | capture() 224 | draw.run() 225 | render() 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/ClipDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | interface ClipDrawable { 4 | fun draw( 5 | x: Float, y: Float, 6 | width: Float, height: Float, 7 | clipLeft: Float, clipRight: Float, 8 | clipTop: Float, clipBottom: Float 9 | ) 10 | fun draw( 11 | x: Float, y: Float, 12 | originX: Float, originY: Float, 13 | width: Float, height: Float, 14 | scaleX: Float, scaleY: Float, 15 | rotation: Float, 16 | clipLeft: Float, clipRight: Float, 17 | clipTop: Float, clipBottom: Float 18 | ) 19 | var leftWidth: Float 20 | var rightWidth: Float 21 | var topHeight: Float 22 | var bottomHeight: Float 23 | var minWidth: Float 24 | var minHeight: Float 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/EdgeLineStripDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.graphics.Color 4 | import arc.graphics.g2d.Draw 5 | import arc.graphics.g2d.Lines 6 | import arc.math.Mathf 7 | import arc.util.Tmp 8 | 9 | 10 | class EdgeLineStripDrawable( 11 | val stroke: Float, 12 | val lineColor: Color, 13 | color: Color = Color.clear, 14 | innerColor: Color = color, 15 | ): FillStripDrawable(color, innerColor) { 16 | override fun draw( 17 | originX: Float, 18 | originY: Float, 19 | angle: Float, 20 | distance: Float, 21 | angleDelta: Float, 22 | stripWidth: Float, 23 | ) { 24 | super.draw(originX, originY, angle, distance, angleDelta, stripWidth) 25 | Draw.color(Tmp.c1.set(lineColor).mul(Draw.getColor()).toFloatBits()) 26 | Lines.stroke(stroke) 27 | if (stripWidth <= 0) { 28 | Lines.arc( 29 | originX, originY, 30 | distance, angleDelta/360f, 31 | angle 32 | ) 33 | } 34 | else if (angleDelta <= 0) { 35 | val cos = Mathf.cosDeg(angle) 36 | val sin = Mathf.sinDeg(angle) 37 | val inOffX = distance * cos 38 | val inOffY = distance * sin 39 | val outOffX = (distance + stripWidth) * cos 40 | val outOffY = (distance + stripWidth) * sin 41 | Lines.line( 42 | originX + inOffX, 43 | originY + inOffY, 44 | originX + outOffX, 45 | originY + outOffY, 46 | ) 47 | } 48 | else DrawUtils.circleFrame( 49 | originX, originY, 50 | distance, distance + stripWidth, 51 | angleDelta, angle, 52 | padCap = true 53 | ) 54 | } 55 | 56 | fun tint( 57 | stroke: Float, 58 | lineColor: Color, 59 | color: Color = this.color, 60 | innerColor: Color = this.innerColor, 61 | ): EdgeLineStripDrawable { 62 | return EdgeLineStripDrawable(stroke, lineColor, color, innerColor) 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/FillStripDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.graphics.Color 4 | import arc.graphics.g2d.Draw 5 | import arc.util.Tmp 6 | 7 | open class FillStripDrawable( 8 | val color: Color, 9 | val innerColor: Color = color, 10 | ): BaseStripDrawable() { 11 | override fun draw( 12 | originX: Float, originY: Float, 13 | angle: Float, distance: Float, 14 | angleDelta: Float, stripWidth: Float 15 | ) { 16 | DrawUtils.circleStrip( 17 | originX, originY, 18 | distance, distance + stripWidth, 19 | angleDelta, angle, 20 | Tmp.c1.set(innerColor).mul(Draw.getColor()), 21 | Tmp.c2.set(color).mul(Draw.getColor()) 22 | ) 23 | } 24 | 25 | open fun tint(color: Color, innerColor: Color = color): FillStripDrawable { 26 | return FillStripDrawable(color, innerColor) 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/HeShaders.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.Core 4 | import arc.files.Fi 5 | import arc.graphics.gl.Shader 6 | import helium.Helium.Companion.getInternalFile 7 | import helium.graphics.g2d.EntityRangeExtractor 8 | 9 | object HeShaders { 10 | lateinit var entityRangeRenderer: EntityRangeExtractor 11 | 12 | lateinit var baseScreen: Shader 13 | 14 | private val internalShaderDir: Fi = getInternalFile("shaders") 15 | 16 | fun load() { 17 | entityRangeRenderer = EntityRangeExtractor() 18 | 19 | baseScreen = Shader( 20 | Core.files.internal("shaders/screenspace.vert"), 21 | internalShaderDir.child("dist_base.frag") 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/LazyDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.func.Prov 4 | import arc.scene.style.Drawable 5 | 6 | class LazyDrawable( 7 | private val prov: Prov 8 | ): Drawable { 9 | private var drawable: Drawable? = null 10 | 11 | private fun check(){ 12 | if (drawable == null) drawable = prov.get() 13 | } 14 | 15 | override fun draw(x: Float, y: Float, width: Float, height: Float) { 16 | check() 17 | drawable!!.draw(x, y, width, height) 18 | } 19 | 20 | override fun draw( 21 | x: Float, y: Float, originX: Float, originY: Float, 22 | width: Float, height: Float, scaleX: Float, scaleY: Float, 23 | rotation: Float, 24 | ) { 25 | check() 26 | drawable!!.draw(x, y, originX, originY, width, height, scaleX, scaleY, rotation) 27 | } 28 | 29 | override fun getLeftWidth() = drawable?.leftWidth?:0f 30 | override fun setLeftWidth(leftWidth: Float) { drawable?.leftWidth = leftWidth } 31 | override fun getRightWidth() = drawable?.rightWidth?:0f 32 | override fun setRightWidth(rightWidth: Float) { drawable?.rightWidth = rightWidth } 33 | override fun getTopHeight() = drawable?.topHeight?:0f 34 | override fun setTopHeight(topHeight: Float) { drawable?.topHeight = topHeight } 35 | override fun getBottomHeight() = drawable?.bottomHeight?:0f 36 | override fun setBottomHeight(bottomHeight: Float) { drawable?.bottomHeight = bottomHeight } 37 | 38 | override fun getMinWidth() = drawable?.minWidth?:0f 39 | override fun setMinWidth(minWidth: Float) { drawable?.minWidth = minWidth } 40 | override fun getMinHeight() = drawable?.minHeight?:0f 41 | override fun setMinHeight(minHeight: Float) { drawable?.minHeight = minHeight } 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/ScaledNinePatchClipDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.graphics.g2d.NinePatch 4 | import arc.scene.style.NinePatchDrawable 5 | import arc.scene.ui.layout.Scl 6 | 7 | open class ScaledNinePatchClipDrawable : NinePatchClipDrawable { 8 | open val scale get() = Scl.scl(1f) 9 | 10 | /** Creates an uninitialized NinePatchDrawable. The ninepatch must be [setPatch] before use. */ 11 | constructor(): super() 12 | constructor(patch: NinePatch): super(patch) 13 | constructor(drawable: NinePatchClipDrawable): super(drawable) 14 | constructor(drawable: NinePatchDrawable): super(drawable.patch!!) 15 | 16 | override fun draw( 17 | x: Float, y: Float, width: Float, height: Float, 18 | clipLeft: Float, clipRight: Float, clipTop: Float, clipBottom: Float 19 | ) { 20 | val scale = scale 21 | draw( 22 | x, y, 0f, 0f, 23 | width/scale, height/scale, scale, scale, 0f, 24 | clipLeft/scale, clipRight/scale, clipTop/scale, clipBottom/scale 25 | ) 26 | } 27 | 28 | override fun setPatch(patch: NinePatch) { 29 | super.setPatch(patch) 30 | 31 | minWidth = patch.totalWidth*scale 32 | minHeight = patch.totalHeight*scale 33 | } 34 | 35 | override var leftWidth: Float 36 | get() = ninePatch.padLeft*scale 37 | set(_) {} 38 | override var rightWidth: Float 39 | get() = ninePatch.padRight*scale 40 | set(_) {} 41 | override var topHeight: Float 42 | get() = ninePatch.padTop*scale 43 | set(_) {} 44 | override var bottomHeight: Float 45 | get() = ninePatch.padBottom*scale 46 | set(_) {} 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/ScreenSampler.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | import arc.Core 4 | import arc.Events 5 | import arc.graphics.Color 6 | import arc.graphics.GL30 7 | import arc.graphics.Gl 8 | import arc.graphics.g2d.Draw 9 | import arc.graphics.gl.FrameBuffer 10 | import arc.graphics.gl.Shader 11 | import arc.util.serialization.Jval 12 | import mindustry.game.EventType 13 | 14 | object ScreenSampler { 15 | private var worldBuffer: FrameBuffer? = null 16 | private var uiBuffer: FrameBuffer? = null 17 | 18 | private var currBuffer: FrameBuffer? = null 19 | private var activity = false 20 | 21 | fun resetMark() { 22 | Core.settings.remove("sampler.setup") 23 | } 24 | 25 | fun setup() { 26 | if (activity) throw RuntimeException("forbid setup sampler twice") 27 | 28 | var e = Jval.read(Core.settings.getString("sampler.setup", "{enabled: false}")) 29 | 30 | if (!e.getBool("enabled", false)) { 31 | e = Jval.newObject() 32 | e.put("enabled", true) 33 | e.put("className", ScreenSampler::class.java.name) 34 | e.put("worldBuffer", "worldBuffer") 35 | e.put("uiBuffer", "uiBuffer") 36 | 37 | worldBuffer = FrameBuffer() 38 | uiBuffer = FrameBuffer() 39 | 40 | Core.settings.put("sampler.setup", e.toString()) 41 | 42 | Events.run(EventType.Trigger.preDraw) { beginWorld() } 43 | Events.run(EventType.Trigger.postDraw) { endWorld() } 44 | 45 | Events.run(EventType.Trigger.uiDrawBegin) { beginUI() } 46 | Events.run(EventType.Trigger.uiDrawEnd) { endUI() } 47 | } 48 | else { 49 | val className = e.getString("className") 50 | val worldBufferName = e.getString("worldBuffer") 51 | val uiBufferName = e.getString("uiBuffer") 52 | val clazz = Class.forName(className) 53 | val worldBufferField = clazz.getDeclaredField(worldBufferName) 54 | val uiBufferField = clazz.getDeclaredField(uiBufferName) 55 | 56 | worldBufferField.isAccessible = true 57 | uiBufferField.isAccessible = true 58 | worldBuffer = worldBufferField[null] as FrameBuffer 59 | uiBuffer = uiBufferField[null] as FrameBuffer 60 | 61 | Events.run(EventType.Trigger.preDraw) { currBuffer = worldBuffer } 62 | Events.run(EventType.Trigger.postDraw) { currBuffer = null } 63 | Events.run(EventType.Trigger.uiDrawBegin) { currBuffer = uiBuffer } 64 | Events.run(EventType.Trigger.uiDrawEnd) { currBuffer = null } 65 | } 66 | 67 | activity = true 68 | } 69 | 70 | private fun beginWorld() { 71 | currBuffer = worldBuffer 72 | worldBuffer!!.resize(Core.graphics.width, Core.graphics.height) 73 | worldBuffer!!.begin(Color.clear) 74 | } 75 | 76 | private fun endWorld() { 77 | currBuffer = null 78 | worldBuffer!!.end() 79 | } 80 | 81 | private fun beginUI() { 82 | currBuffer = uiBuffer 83 | uiBuffer!!.resize(Core.graphics.width, Core.graphics.height) 84 | uiBuffer!!.begin(Color.clear) 85 | blitBuffer(worldBuffer!!, uiBuffer) 86 | } 87 | 88 | private fun endUI() { 89 | currBuffer = null 90 | uiBuffer!!.end() 91 | blitBuffer(uiBuffer!!, null) 92 | } 93 | 94 | /**将当前的屏幕纹理使用传入的着色器绘制到屏幕上 95 | * @param unit 屏幕采样纹理绑定的纹理单元 96 | */ 97 | fun blit(shader: Shader, unit: Int = 0) { 98 | checkNotNull(currBuffer) { "currently no buffer bound" } 99 | 100 | currBuffer!!.texture.bind(unit) 101 | Draw.blit(shader) 102 | } 103 | 104 | private fun blitBuffer(from: FrameBuffer, to: FrameBuffer?) { 105 | if (Core.gl30 == null) { 106 | from.blit(HeShaders.baseScreen) 107 | } 108 | else { 109 | Gl.bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, from.framebufferHandle) 110 | Gl.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, to?.framebufferHandle ?: 0) 111 | Core.gl30.glBlitFramebuffer( 112 | 0, 0, from.width, from.height, 113 | 0, 0, 114 | to?.width ?: Core.graphics.width, 115 | to?.height ?: Core.graphics.height, 116 | Gl.colorBufferBit, Gl.nearest 117 | ) 118 | } 119 | } 120 | 121 | /**将当前屏幕纹理转存到一个[帧缓冲区][FrameBuffer],这将成为一份拷贝,可用于暂存屏幕内容 122 | * 123 | * @param target 用于转存屏幕纹理的目标缓冲区 124 | * @param clear 在转存之前是否清空帧缓冲区 125 | */ 126 | fun getToBuffer(target: FrameBuffer, clear: Boolean) { 127 | checkNotNull(currBuffer) { "currently no buffer bound" } 128 | 129 | if (clear) target.begin(Color.clear) 130 | else target.begin() 131 | 132 | Gl.bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, currBuffer!!.framebufferHandle) 133 | Gl.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, target.framebufferHandle) 134 | Core.gl30.glBlitFramebuffer( 135 | 0, 0, currBuffer!!.width, currBuffer!!.height, 136 | 0, 0, target.width, target.height, 137 | Gl.colorBufferBit, Gl.nearest 138 | ) 139 | 140 | target.end() 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/StripDrawable.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics 2 | 3 | interface StripDrawable { 4 | fun draw(originX: Float, originY: Float, stripWidth: Float, angleDelta: Float) = 5 | draw(originX, originY, 0f, 0f, angleDelta, stripWidth) 6 | fun draw(originX: Float, originY: Float, angle: Float, distance: Float, angleDelta: Float, stripWidth: Float) 7 | var leftOff: Float 8 | var rightOff: Float 9 | var outerWidth: Float 10 | var innerWidth: Float 11 | var minOffset: Float 12 | var minWidth: Float 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/graphics/g2d/EntityRangeExtractor.kt: -------------------------------------------------------------------------------- 1 | package helium.graphics.g2d 2 | 3 | import arc.Core 4 | import arc.files.Fi 5 | import arc.graphics.Color 6 | import arc.graphics.Texture 7 | import arc.graphics.gl.FrameBuffer 8 | import arc.graphics.gl.Shader 9 | import arc.util.Time 10 | import helium.Helium.Companion.getInternalFile 11 | 12 | class EntityRangeExtractor { 13 | companion object { 14 | private val internalShaderDir: Fi = getInternalFile("shaders") 15 | } 16 | 17 | private val buffer = FrameBuffer().also { it.texture.setFilter(Texture.TextureFilter.nearest) } 18 | init { 19 | setupShader() 20 | } 21 | 22 | private var capturing = false 23 | 24 | var stroke = 2f 25 | var alpha = 0.075f 26 | 27 | private lateinit var extractShader: Shader 28 | 29 | private fun setupShader() { 30 | extractShader = Shader( 31 | Core.files.internal("shaders/screenspace.vert"), 32 | internalShaderDir.child("entity_range.frag") 33 | ) 34 | buffer.resize(Core.graphics.width, Core.graphics.height) 35 | } 36 | 37 | fun capture(){ 38 | if (capturing) throw IllegalStateException("capturing already running") 39 | 40 | buffer.resize(Core.graphics.width, Core.graphics.height) 41 | buffer.begin(Color.clear) 42 | capturing = true 43 | } 44 | 45 | fun render(){ 46 | if (!capturing) throw IllegalStateException("capturing not started") 47 | 48 | capturing = false 49 | 50 | buffer.end() 51 | extractShader.also{ 52 | it.bind() 53 | it.apply() 54 | it.setUniformf("u_time", Time.time) 55 | it.setUniformf("u_resolution", Core.camera.width, Core.camera.height) 56 | it.setUniformf( 57 | "u_campos", 58 | Core.camera.position.x - Core.camera.width/2, 59 | Core.camera.position.y - Core.camera.height/2 60 | ) 61 | it.setUniformf("u_stroke", stroke) 62 | it.setUniformf("u_alpha", alpha) 63 | it.setUniformi("u_texture", 0) 64 | buffer.blit(it) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/HeAssets.kt: -------------------------------------------------------------------------------- 1 | package helium.ui 2 | 3 | import arc.graphics.Color 4 | import arc.graphics.g2d.Lines 5 | import arc.scene.style.BaseDrawable 6 | import arc.scene.style.Drawable 7 | import arc.scene.style.TextureRegionDrawable 8 | import arc.scene.ui.layout.Scl 9 | import arc.util.Time 10 | import arc.util.Tmp 11 | import helium.Helium 12 | import helium.graphics.DrawUtils 13 | import helium.graphics.EdgeLineStripDrawable 14 | import helium.graphics.FillStripDrawable 15 | import helium.graphics.StripDrawable 16 | import mindustry.gen.Tex 17 | import mindustry.graphics.Pal 18 | 19 | object HeAssets { 20 | val lightBlue = Color.valueOf("D3FDFF") 21 | 22 | lateinit var heIcon: Drawable 23 | lateinit var program: Drawable 24 | lateinit var java: Drawable 25 | lateinit var javascript: Drawable 26 | 27 | lateinit var loading: Drawable 28 | lateinit var networkError: Drawable 29 | 30 | lateinit var transparent: Drawable 31 | lateinit var grayUI: Drawable 32 | lateinit var grayUIAlpha: Drawable 33 | lateinit var darkGrayUI: Drawable 34 | lateinit var darkGrayUIAlpha: Drawable 35 | lateinit var padGrayUIAlpha: Drawable 36 | lateinit var slotsBack: Drawable 37 | 38 | lateinit var whiteStrip: StripDrawable 39 | lateinit var whiteEdge: StripDrawable 40 | lateinit var innerLight: StripDrawable 41 | lateinit var outerLight: StripDrawable 42 | 43 | fun load(){ 44 | heIcon = Helium.getDrawable("helium") 45 | program = Helium.getDrawable("program") 46 | java = Helium.getDrawable("java") 47 | javascript = Helium.getDrawable("javascript") 48 | 49 | loading = object: BaseDrawable(){ 50 | override fun draw(x: Float, y: Float, width: Float, height: Float) { 51 | val r1 = Time.globalTime*2 52 | val r2 = Time.globalTime 53 | val ang = (r1 - r2)%720 - 360f 54 | Lines.stroke(Scl.scl(4f)) 55 | DrawUtils.arc( 56 | x + width/2, y + height/2, 57 | width/2.3f, ang, r1 58 | ) 59 | } 60 | } 61 | networkError = Helium.getDrawable("network-error") 62 | 63 | slotsBack = Helium.getDrawable("slots-back") 64 | 65 | val white = Tex.whiteui as TextureRegionDrawable 66 | transparent = white.tint(Color.clear) 67 | grayUI = white.tint(Pal.darkerGray) 68 | grayUIAlpha = white.tint(Tmp.c1.set(Pal.darkerGray).a(0.7f)) 69 | darkGrayUI = white.tint(Pal.darkestGray) 70 | darkGrayUIAlpha = white.tint(Tmp.c1.set(Pal.darkestGray).a(0.7f)) 71 | padGrayUIAlpha = white.tint(Tmp.c1.set(Pal.darkerGray).a(0.7f)).also { 72 | it.leftWidth = 8f 73 | it.rightWidth = 8f 74 | it.topHeight = 8f 75 | it.bottomHeight = 8f 76 | } 77 | 78 | whiteStrip = FillStripDrawable(Color.white) 79 | whiteEdge = EdgeLineStripDrawable(Scl.scl(3f), Color.white) 80 | innerLight = FillStripDrawable(Color.white.cpy().a(0f), Color.white) 81 | outerLight = FillStripDrawable(Color.white, Color.white.cpy().a(0f)) 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/HeStyles.kt: -------------------------------------------------------------------------------- 1 | package helium.ui 2 | 3 | import arc.Core 4 | import arc.Events 5 | import arc.graphics.Color 6 | import arc.scene.style.Drawable 7 | import arc.scene.style.NinePatchDrawable 8 | import arc.scene.style.TextureRegionDrawable 9 | import arc.scene.ui.Dialog.DialogStyle 10 | import arc.scene.ui.layout.Scl 11 | import helium.Helium 12 | import helium.graphics.* 13 | import helium.ui.HeAssets.transparent 14 | import helium.ui.elements.roulette.StripButtonStyle 15 | import helium.ui.fragments.entityinfo.displays.HealthBarStyle 16 | import mindustry.game.EventType 17 | import mindustry.graphics.Pal 18 | import mindustry.ui.Fonts 19 | import mindustry.ui.Styles 20 | 21 | object HeStyles { 22 | lateinit var BLUR_BACK: Drawable 23 | 24 | lateinit var blurStrip: StripDrawable 25 | 26 | lateinit var none: StripDrawable 27 | lateinit var black: StripDrawable 28 | lateinit var black9: StripDrawable 29 | lateinit var black8: StripDrawable 30 | lateinit var black7: StripDrawable 31 | lateinit var black6: StripDrawable 32 | lateinit var black5: StripDrawable 33 | lateinit var boundBlack: StripDrawable 34 | lateinit var boundBlack9: StripDrawable 35 | lateinit var boundBlack8: StripDrawable 36 | lateinit var boundBlack7: StripDrawable 37 | lateinit var boundBlack6: StripDrawable 38 | lateinit var boundBlack5: StripDrawable 39 | lateinit var grayPanel: StripDrawable 40 | lateinit var flatOver: StripDrawable 41 | lateinit var edgeFlatOver: StripDrawable 42 | lateinit var flatDown: StripDrawable 43 | lateinit var clearEdge: StripDrawable 44 | lateinit var accent: StripDrawable 45 | 46 | lateinit var transparentBack: DialogStyle 47 | 48 | lateinit var test: HealthBarStyle 49 | 50 | lateinit var clearS: StripButtonStyle 51 | lateinit var toggleClearS: StripButtonStyle 52 | lateinit var boundClearS: StripButtonStyle 53 | lateinit var flatS: StripButtonStyle 54 | lateinit var grayS: StripButtonStyle 55 | 56 | var uiBlur: Blur = Blur(*DEf_B) 57 | 58 | private var drawingCounter = 0 59 | private var lastDialogs = 0 60 | 61 | fun load() { 62 | Events.run(EventType.Trigger.uiDrawBegin) { drawingCounter = 0 } 63 | Events.run(EventType.Trigger.uiDrawEnd) { lastDialogs = drawingCounter } 64 | 65 | BLUR_BACK = object : TextureRegionDrawable(Core.atlas.white()) { 66 | override fun draw(x: Float, y: Float, width: Float, height: Float) { 67 | drawingCounter++ 68 | if (drawingCounter == lastDialogs) uiBlur.directDraw { 69 | super.draw(x, y, width, height) 70 | } 71 | 72 | Styles.black5.draw(x, y, width, height) 73 | } 74 | 75 | override fun draw( 76 | x: Float, y: Float, originX: Float, originY: Float, 77 | width: Float, height: Float, 78 | scaleX: Float, scaleY: Float, 79 | rotation: Float, 80 | ) { 81 | drawingCounter++ 82 | if (drawingCounter == lastDialogs) uiBlur.directDraw { 83 | super.draw( 84 | x, y, originX, originY, 85 | width, height, 86 | scaleX, scaleY, 87 | rotation 88 | ) 89 | } 90 | 91 | Styles.black5.draw(x, y, originX, originY, width, height, scaleX, scaleY, rotation) 92 | } 93 | } 94 | 95 | blurStrip = object: FillStripDrawable(Color.white){ 96 | override fun draw( 97 | originX: Float, 98 | originY: Float, 99 | angle: Float, 100 | distance: Float, 101 | angleDelta: Float, 102 | stripWidth: Float, 103 | ) { 104 | drawingCounter++ 105 | if (drawingCounter == lastDialogs) uiBlur.directDraw { 106 | super.draw( 107 | originX, originY, 108 | angle, distance, 109 | angleDelta, stripWidth, 110 | ) 111 | } 112 | } 113 | } 114 | 115 | none = FillStripDrawable(Color.clear) 116 | black = FillStripDrawable(Color.black) 117 | black9 = FillStripDrawable(Color.black.cpy().a(0.9f)) 118 | black8 = FillStripDrawable(Color.black.cpy().a(0.8f)) 119 | black7 = FillStripDrawable(Color.black.cpy().a(0.6f)) 120 | black6 = FillStripDrawable(Color.black.cpy().a(0.5f)) 121 | black5 = FillStripDrawable(Color.black.cpy().a(0.3f)) 122 | boundBlack = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black) 123 | boundBlack9 = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black.cpy().a(0.9f)) 124 | boundBlack8 = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black.cpy().a(0.8f)) 125 | boundBlack7 = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black.cpy().a(0.6f)) 126 | boundBlack6 = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black.cpy().a(0.5f)) 127 | boundBlack5 = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.black.cpy().a(0.3f)) 128 | grayPanel = FillStripDrawable(Pal.darkestGray) 129 | flatOver = FillStripDrawable(Color.valueOf("454545").a(0.6f)) 130 | flatDown = EdgeLineStripDrawable(Scl.scl(3f), Pal.accent) 131 | edgeFlatOver = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray, Color.valueOf("454545")) 132 | clearEdge = EdgeLineStripDrawable(Scl.scl(3f), Pal.darkestGray) 133 | accent = FillStripDrawable(Pal.accent) 134 | 135 | transparentBack = object : DialogStyle() { 136 | init { 137 | stageBackground = transparent 138 | titleFont = Fonts.outline 139 | background = transparent 140 | titleFontColor = Pal.accent 141 | } 142 | } 143 | 144 | test = HealthBarStyle( 145 | background = ScaledNinePatchClipDrawable(Helium.getDrawable("background") as NinePatchDrawable), 146 | backgroundColor = Color.white.cpy().a(0.6f), 147 | healthBar = ScaledNinePatchClipDrawable(Helium.getDrawable("healthbar") as NinePatchDrawable), 148 | healthPadLeft = 6, 149 | healthPadRight = 6, 150 | shieldBar = ScaledNinePatchClipDrawable(Helium.getDrawable("shieldbar") as NinePatchDrawable), 151 | shieldPadLeft = 5, 152 | shieldPadRight = 5, 153 | font = Fonts.outline, 154 | texOffX = 15, 155 | texOffY = 7, 156 | shieldsOffX = 15, 157 | shieldsOffY = 7 158 | ) 159 | 160 | flatS = StripButtonStyle( 161 | over = flatOver, 162 | down = flatOver, 163 | up = black, 164 | ) 165 | grayS = StripButtonStyle( 166 | over = flatOver, 167 | down = flatOver, 168 | up = grayPanel, 169 | ) 170 | clearS = StripButtonStyle( 171 | down = flatDown, 172 | up = none, 173 | over = flatOver, 174 | ) 175 | toggleClearS = StripButtonStyle( 176 | down = flatDown, 177 | up = none, 178 | over = flatOver, 179 | checked = flatDown, 180 | checkedOver = EdgeLineStripDrawable(Scl.scl(3f), Pal.accent, Color.valueOf("454545").a(0.6f)), 181 | ) 182 | boundClearS = StripButtonStyle( 183 | down = flatDown, 184 | up = clearEdge, 185 | over = edgeFlatOver, 186 | ) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/UIUtils.kt: -------------------------------------------------------------------------------- 1 | package helium.ui 2 | 3 | import arc.Core 4 | import arc.func.Boolp 5 | import arc.func.Cons 6 | import arc.func.Cons2 7 | import arc.func.Prov 8 | import arc.graphics.Color 9 | import arc.scene.style.Drawable 10 | import arc.scene.ui.Dialog 11 | import arc.scene.ui.Image 12 | import arc.scene.ui.layout.Cell 13 | import arc.scene.ui.layout.Table 14 | import arc.util.Align 15 | import arc.util.Strings 16 | import helium.invoke 17 | import mindustry.gen.Icon 18 | import mindustry.graphics.Pal 19 | import mindustry.ui.Styles 20 | 21 | object UIUtils { 22 | val cancelBut = ButtonEntry(Core.bundle["cancel"], Icon.cancel, clicked = { it.hide() }) 23 | val closeBut = ButtonEntry(Core.bundle["misc.close"], Icon.cancel, clicked = { it.hide() }) 24 | 25 | fun showException(e: Throwable, message: String? = null) = showPane( 26 | Core.bundle["infos.exception"], 27 | ButtonEntry(Core.bundle["confirm"], Icon.ok) { it.hide() }, 28 | titleColor = Color.crimson, 29 | ){ t -> 30 | message?.also { 31 | t.add(it) 32 | t.row() 33 | } 34 | t.row() 35 | t.pane(Styles.smallPane) { pane -> 36 | pane.add(Strings.neatError(e)).color(Color.lightGray).left().fill() 37 | }.fill().margin(14f).maxHeight(800f) 38 | } 39 | 40 | fun showError(message: String) = showPane( 41 | Core.bundle["infos.error"], 42 | ButtonEntry(Core.bundle["confirm"], Icon.ok) { it.hide() }, 43 | titleColor = Color.crimson, 44 | ){ t -> 45 | t.add(message) 46 | t.row() 47 | } 48 | 49 | fun showInput( 50 | title: String?, 51 | message: String, 52 | area: Boolean = false, 53 | buildContent: Cons? = null, 54 | callback: Cons2 55 | ): Dialog { 56 | var text = "" 57 | 58 | return showPane( 59 | title, 60 | cancelBut, 61 | ButtonEntry(Core.bundle["confirm"], Icon.ok) { 62 | callback(it, text) 63 | }, 64 | ){ info -> 65 | info.add(message).growX().labelAlign(Align.left) 66 | info.row() 67 | info.table{ inp -> 68 | val field = if (area) inp.area(""){ text = it }.grow().get() 69 | else inp.field(""){ text = it }.growX().fillY().get() 70 | 71 | inp.button(Icon.paste, Styles.clearNonei, 32f) { 72 | field.text = Core.app.clipboardText 73 | text = field.text 74 | }.size(48f) 75 | }.grow() 76 | buildContent?.also { 77 | info.row() 78 | info.table(it).growX().fillY() 79 | } 80 | } 81 | } 82 | 83 | fun showTip(title: String?, message: String, callback: Runnable? = null) = showPane( 84 | title, 85 | ButtonEntry(Core.bundle["confirm"], Icon.cancel) { 86 | callback?.run() 87 | it.hide() 88 | }, 89 | ){ it.add(message) } 90 | 91 | fun showConfirm(title: String?, message: String, callback: Runnable) = showPane( 92 | title, 93 | cancelBut, 94 | ButtonEntry(Core.bundle["confirm"], Icon.ok) { 95 | callback.run() 96 | it.hide() 97 | } 98 | ){ it.add(message) } 99 | 100 | fun showPane( 101 | title: String?, 102 | vararg buttons: ButtonEntry, 103 | titleColor: Color? = Pal.accent, 104 | build: Cons
105 | ): Dialog { 106 | val dialog = Dialog() 107 | dialog.cont.table(HeAssets.grayUIAlpha){ info -> 108 | title?.also { 109 | info.add(it).color(titleColor).pad(8f) 110 | info.row() 111 | info.line(Pal.accent, true, 3f).padTop(4f).padLeft(-6f).padRight(-6f) 112 | info.row() 113 | } 114 | info.pane(build).grow().minSize(360f, 100f).pad(12f) 115 | info.row() 116 | info.table { but -> 117 | but.defaults().growX().height(46f).minWidth(82f).pad(4f) 118 | buttons.forEach { entry -> 119 | but.button("", Styles.none, Styles.flatt, 32f) { 120 | entry.clicked(dialog) 121 | }.margin(6f).also { 122 | entry.disabled?.also { d -> it.disabled { d.get() } } 123 | entry.checked?.also { c -> it.checked { c.get() } } 124 | }.update { b -> 125 | b.setText(entry.title.get()) 126 | entry.icon?.also { i -> 127 | val img = b.find{ it is Image } 128 | img.setDrawable(i.get()) 129 | } 130 | } 131 | } 132 | }.growX().fillY() 133 | }.margin(6f).grow() 134 | 135 | return dialog.show() 136 | } 137 | 138 | fun Table.line(color: Color, horizon: Boolean, stroke: Float) = image().color(color).also { 139 | if (horizon) it.height(stroke).growX() 140 | else it.width(stroke).growY() 141 | } as Cell 142 | } 143 | 144 | data class ButtonEntry( 145 | val title: Prov, 146 | val icon: Prov? = null, 147 | val disabled: Boolp? = null, 148 | val checked: Boolp? = null, 149 | val clicked: Cons, 150 | ){ 151 | constructor( 152 | title: String, 153 | icon: Drawable? = null, 154 | disabled: Boolp? = null, 155 | checked: Boolp? = null, 156 | clicked: Cons, 157 | ) : this( 158 | { title }, 159 | icon?.let { { it } }, 160 | disabled, 161 | checked, 162 | clicked, 163 | ) 164 | } 165 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/ConfigLayouts.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs 2 | 3 | import arc.Core 4 | import arc.func.* 5 | import arc.graphics.Color 6 | import arc.input.KeyCode 7 | import arc.math.Mathf 8 | import arc.scene.event.HandCursorListener 9 | import arc.scene.event.InputEvent 10 | import arc.scene.event.InputListener 11 | import arc.scene.event.Touchable 12 | import arc.scene.ui.* 13 | import arc.scene.ui.layout.Cell 14 | import arc.scene.ui.layout.Table 15 | import arc.util.Strings 16 | import arc.util.Time 17 | import helium.invoke 18 | import helium.ui.UIUtils.line 19 | import helium.ui.dialogs.ModConfigDialog.ConfigLayout 20 | import mindustry.graphics.Pal 21 | import mindustry.ui.Styles 22 | import helium.util.binds.CombinedKeys 23 | import kotlin.reflect.KMutableProperty0 24 | 25 | class ConfigSepLine( 26 | name: String, 27 | private var string: String, 28 | private var lineColor: Color = Pal.accent, 29 | private var lineColorBack: Color = Pal.accentBack 30 | ): ConfigLayout(name) { 31 | override fun build(table: Table) { 32 | table.stack( 33 | Table { t -> 34 | t.image().color(lineColor).pad(0f).grow() 35 | t.row() 36 | t.line(lineColorBack, true, 4f) 37 | }, 38 | Table { t: Table -> 39 | t.left().add(string, Styles.outlineLabel).fill().left().padLeft(5f) 40 | } 41 | ).grow().pad(-5f).padBottom(4f).padTop(4f) 42 | table.row() 43 | } 44 | } 45 | 46 | abstract class ConfigEntry(name: String) : ConfigLayout(name) { 47 | protected var str: Prov? = null 48 | protected var tip: Prov? = null 49 | protected var disabled = Boolp { false } 50 | 51 | init { 52 | if (Core.bundle.has("settings.tip.$name")) { 53 | tip = Prov { Core.bundle["settings.tip.$name"] } 54 | } 55 | } 56 | 57 | override fun build(table: Table) { 58 | table.left().add(Core.bundle["settings.item.$name"]).left().padLeft(4f) 59 | table.table { t: Table -> 60 | t.clip = false 61 | t.right().defaults().right().padRight(0f) 62 | if (str != null) t.add("").update { l -> l.setText(str!!.get()) } 63 | 64 | buildCfg(t) 65 | }.growX().height(60f).padRight(4f).right() 66 | 67 | if (tip != null) { 68 | table.addListener(Tooltip { ta -> 69 | ta.add( 70 | tip!!.get() 71 | ).update { l: Label -> l.setText(tip!!.get()) } 72 | }.apply { allowMobile = true }) 73 | } 74 | } 75 | 76 | abstract fun buildCfg(table: Table) 77 | } 78 | 79 | class ConfigButton( 80 | name: String, 81 | private var button: Prov
, 91 | private var handler: Cons> 92 | ): ConfigEntry(name) { 93 | override fun buildCfg(table: Table) { 94 | handler[table.table { t: Table -> 95 | t.clip = false 96 | this.table[t] 97 | }] 98 | } 99 | } 100 | 101 | class ConfigKeyBind( 102 | name: String, 103 | private var getBindKey: Prov, 104 | private var bindingKey: Cons, 105 | ): ConfigEntry(name) { 106 | val def = getBindKey.get()!! 107 | 108 | constructor(name: String, baindField: KMutableProperty0): this( 109 | name, 110 | { baindField.get() }, 111 | { baindField.set(it) } 112 | ) 113 | 114 | override fun buildCfg(table: Table) { 115 | createKeybindTable(table, getBindKey, bindingKey) { bindingKey.get(def) } 116 | } 117 | 118 | private fun createKeybindTable( 119 | table: Table, 120 | getBindKey: Prov, 121 | hotKeyMethod: Cons, 122 | resetMethod: Runnable 123 | ) { 124 | table.label{ getBindKey().toString() }.color(Pal.accent).left().minWidth(160f).padRight(20f) 125 | 126 | table.button("@settings.rebind", Styles.defaultt) { openDialog(false){ hotKeyMethod(it.first()) } }.width(130f) 127 | table.button("@settings.resetKey", Styles.defaultt) { resetMethod.run() }.width(130f).pad(2f).padLeft(4f) 128 | table.row() 129 | } 130 | } 131 | 132 | private fun openDialog(isCombine: Boolean, callBack: Cons>) { 133 | val rebindDialog = Dialog() 134 | val res = linkedSetOf() 135 | var show = "" 136 | 137 | rebindDialog.cont.table{ 138 | it.add(Core.bundle["misc.pressAnyKeys".takeIf { isCombine }?:"keybind.press"]) 139 | .color(Pal.accent) 140 | if (!isCombine) return@table 141 | 142 | it.row() 143 | it.label{ show.ifBlank { Core.bundle["misc.requireInput"] } } 144 | .padTop(8f) 145 | } 146 | 147 | rebindDialog.titleTable.cells.first().pad(4f) 148 | 149 | rebindDialog.addListener(object : InputListener() { 150 | override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: KeyCode): Boolean { 151 | if (Core.app.isAndroid) { 152 | rebindDialog.hide() 153 | return false 154 | } 155 | 156 | keySet(button) 157 | return true 158 | } 159 | 160 | override fun keyDown(event: InputEvent, keycode: KeyCode): Boolean { 161 | if (keycode == KeyCode.escape) { 162 | rebindDialog.hide() 163 | return false 164 | } 165 | 166 | keySet(keycode) 167 | return true 168 | } 169 | 170 | private fun keySet(button: KeyCode) { 171 | if (!isCombine) { 172 | callBack(arrayOf(button)) 173 | rebindDialog.hide() 174 | return 175 | } 176 | 177 | if (button != KeyCode.enter) { 178 | res.add(button) 179 | show = CombinedKeys.toString(res) 180 | } 181 | else { 182 | callBack(res.toTypedArray()) 183 | rebindDialog.hide() 184 | } 185 | } 186 | 187 | override fun keyUp(event: InputEvent?, keycode: KeyCode?): Boolean { 188 | res.remove(keycode) 189 | show = CombinedKeys.toString(res) 190 | 191 | return true 192 | } 193 | }) 194 | 195 | rebindDialog.show() 196 | Time.runTask(1f) { Core.scene.setScrollFocus(rebindDialog) } 197 | } 198 | 199 | class ConfigCheck( 200 | name: String, 201 | private var click: Boolc, 202 | private var checked: Boolp 203 | ): ConfigEntry(name) { 204 | constructor(name: String, field: KMutableProperty0): this(name, { field.set(it) }, { field.get() }) 205 | 206 | override fun buildCfg(table: Table) { 207 | table.check("", checked.get(), click) 208 | .update { c -> c.isChecked = checked.get() } 209 | .get().also { 210 | it.setDisabled(disabled) 211 | table.touchable = Touchable.enabled 212 | table.addListener(it.clickListener) 213 | table.addListener(HandCursorListener()) 214 | it.removeListener(it.clickListener) 215 | } 216 | } 217 | } 218 | 219 | class ConfigSlider : ConfigEntry { 220 | private var slided: Floatc 221 | private var curr: Floatp 222 | private var show: Func 223 | private var min: Float 224 | private var max: Float 225 | private var step: Float 226 | 227 | constructor( 228 | name: String, 229 | slided: Floatc, 230 | curr: Floatp, 231 | min: Float, max: Float, step: Float 232 | ) : super(name) { 233 | var s = step 234 | this.slided = slided 235 | this.curr = curr 236 | this.min = min 237 | this.max = max 238 | this.step = s 239 | 240 | val fix: Int 241 | s %= 1f 242 | var i = 0 243 | while (true) { 244 | if (Mathf.zero(s)) { 245 | fix = i 246 | break 247 | } 248 | s *= 10f 249 | s %= 1f 250 | i++ 251 | } 252 | 253 | this.show = Func { f -> Strings.autoFixed(f, fix) } 254 | } 255 | 256 | constructor( 257 | name: String, 258 | field: KMutableProperty0, 259 | min: Float, max: Float, step: Float 260 | ) : this(name, { field.set(it) }, { field.get() }, min, max, step) 261 | 262 | constructor( 263 | name: String, 264 | field: KMutableProperty0, 265 | min: Int, max: Int, step: Int 266 | ) : this(name, { field.set(it.toInt()) }, { field.get().toFloat() }, min.toFloat(), max.toFloat(), step.toFloat()) 267 | 268 | constructor( 269 | name: String, 270 | slided: Floatc, 271 | curr: Floatp, 272 | min: Float, max: Float, step: Float, 273 | show: Func, 274 | ) : super(name) { 275 | this.show = show 276 | this.slided = slided 277 | this.curr = curr 278 | this.min = min 279 | this.max = max 280 | this.step = step 281 | } 282 | 283 | constructor( 284 | name: String, 285 | field: KMutableProperty0, 286 | min: Float, max: Float, step: Float, 287 | show: Func, 288 | ) : this(name, { field.set(it) }, { field.get() }, min, max, step, show) 289 | 290 | override fun buildCfg(table: Table) { 291 | if (str == null) { 292 | table.add("").update { l: Label -> 293 | l.setText(show[curr.get()]) 294 | }.padRight(0f) 295 | } 296 | table.slider(min, max, step, curr.get(), slided).width(360f).padLeft(4f).update { s: Slider -> 297 | s.setValue(curr.get()) 298 | s.isDisabled = disabled.get() 299 | } 300 | } 301 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/ModConfigDialog.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs 2 | 3 | import arc.Core 4 | import arc.func.Cons 5 | import arc.graphics.Color 6 | import arc.math.Mathf 7 | import arc.scene.Element 8 | import arc.scene.actions.Actions 9 | import arc.scene.style.Drawable 10 | import arc.scene.style.TextureRegionDrawable 11 | import arc.scene.ui.ScrollPane 12 | import arc.scene.ui.TextButton 13 | import arc.scene.ui.TextButton.TextButtonStyle 14 | import arc.scene.ui.layout.Scl 15 | import arc.scene.ui.layout.Table 16 | import arc.struct.ObjectMap 17 | import arc.struct.OrderedMap 18 | import arc.struct.Seq 19 | import arc.util.Align 20 | import helium.He 21 | import helium.Helium 22 | import helium.ui.HeAssets 23 | import helium.ui.HeStyles 24 | import helium.ui.UIUtils.line 25 | import mindustry.Vars 26 | import mindustry.gen.Icon 27 | import mindustry.gen.Tex 28 | import mindustry.graphics.Pal 29 | import mindustry.ui.Fonts 30 | import mindustry.ui.Styles 31 | import mindustry.ui.dialogs.BaseDialog 32 | 33 | class ModConfigDialog : BaseDialog("") { 34 | private var entries: OrderedMap> = OrderedMap() 35 | private var icons: ObjectMap = ObjectMap() 36 | 37 | private var scrollPane: ScrollPane? = null 38 | private var settings: Table? = null 39 | private var hover: Table? = null 40 | 41 | private var currCat: String? = null 42 | 43 | private lateinit var catTable: Table 44 | private lateinit var relaunchTip: Table 45 | private var currIndex: Int = 0 46 | 47 | private var requireRelaunch: Boolean = false 48 | 49 | private val relaunchDialog = object : BaseDialog("") { 50 | init { 51 | style = HeStyles.transparentBack 52 | 53 | cont.table { t -> 54 | t.add(Core.bundle["infos.relaunchEnsure"]) 55 | .padBottom(12f).center().labelAlign(Align.center).grow() 56 | t.row() 57 | t.table { bu -> 58 | bu.defaults().size(230f, 54f) 59 | bu.button(Core.bundle["misc.later"], Icon.left, Styles.flatt) { this.hide() } 60 | .margin(6f) 61 | bu.button(Core.bundle["misc.exitGame"], Icon.ok, Styles.flatt) { Core.app.exit() } 62 | .margin(6f) 63 | }.fill() 64 | }.fill().margin(16f) 65 | } 66 | } 67 | 68 | init { 69 | titleTable.clear() 70 | 71 | addCloseButton() 72 | buttons.button( 73 | Core.bundle["misc.recDefault"], Icon.redo 74 | ) { 75 | Vars.ui.showConfirm( 76 | Core.bundle["infos.confirmResetConfig"] 77 | ) { He.config.reset() } 78 | } 79 | 80 | hidden { 81 | He.config.save() 82 | if (requireRelaunch) { 83 | relaunchDialog.show() 84 | } 85 | } 86 | 87 | resized { this.rebuild() } 88 | shown { this.rebuild() } 89 | } 90 | 91 | fun show(itemName: String){ 92 | show() 93 | Core.app.post { 94 | val elem: Element = settings?.find(itemName)?:return@post 95 | 96 | scrollPane!!.scrollY = elem.y 97 | } 98 | } 99 | 100 | private fun rebuild() { 101 | clearHover() 102 | 103 | cont.clearChildren() 104 | cont.table(Tex.pane) { main -> 105 | main.table { cats -> 106 | if (Scl.scl((entries.size*280).toFloat()) > Core.graphics.width*0.85f) { 107 | val rebuild = Runnable { 108 | currCat = entries.orderedKeys()[currIndex] 109 | catTable.clearActions() 110 | catTable.actions( 111 | Actions.alpha(0f, 0.3f), 112 | Actions.run { 113 | catTable.clearChildren() 114 | catTable.image(icons.get(currCat){ Helium.getDrawable("settings_$currCat") }).size(38f) 115 | catTable.add(Core.bundle["settings.category.$currCat"]) 116 | }, 117 | Actions.alpha(1f, 0.3f) 118 | ) 119 | } 120 | 121 | cats.button(Icon.leftOpen, Styles.clearNonei) { 122 | currIndex = Mathf.mod(currIndex - 1, entries.size) 123 | rebuild.run() 124 | settings!!.clearActions() 125 | settings!!.actions( 126 | Actions.alpha(0f, 0.3f), 127 | Actions.run { this.rebuildSettings() }, 128 | Actions.alpha(1f, 0.3f) 129 | ) 130 | }.size(60f).padLeft(12f) 131 | cats.table(Tex.underline) { t -> catTable = t } 132 | .height(60f).growX().padLeft(4f).padRight(4f) 133 | cats.button(Icon.rightOpen, Styles.clearNonei) { 134 | currIndex = Mathf.mod(currIndex + 1, entries.size) 135 | rebuild.run() 136 | settings!!.clearActions() 137 | settings!!.actions( 138 | Actions.alpha(0f, 0.3f), 139 | Actions.run { this.rebuildSettings() }, 140 | Actions.alpha(1f, 0.3f) 141 | ) 142 | }.size(60f).padRight(12f) 143 | 144 | rebuild.run() 145 | } 146 | else { 147 | cats.defaults().height(60f).growX().padLeft(2f).padRight(2f) 148 | for (key in entries.keys()) { 149 | cats.button( 150 | Core.bundle["settings.category.$key"], 151 | icons[key, Core.atlas.drawable("settings_$key")], 152 | object : TextButtonStyle() { 153 | init { 154 | font = Fonts.def 155 | fontColor = Color.white 156 | disabledFontColor = Color.lightGray 157 | down = Styles.flatOver 158 | checked = Styles.flatOver 159 | up = Tex.underline 160 | over = Tex.underlineOver 161 | disabled = Tex.underlineDisabled 162 | } 163 | }, 164 | 38f 165 | ) { 166 | currCat = key 167 | settings!!.clearActions() 168 | settings!!.actions( 169 | Actions.alpha(0f, 0.3f), 170 | Actions.run { this.rebuildSettings() }, 171 | Actions.alpha(1f, 0.3f) 172 | ) 173 | }.update { b: TextButton -> b.isChecked = key == currCat }.margin(12f) 174 | } 175 | } 176 | }.growX().fillY() 177 | main.row() 178 | main.line(Color.gray, true, 4f).pad(-6f).padTop(4f).padBottom(4f) 179 | main.row() 180 | scrollPane = main.top().pane { pane -> 181 | pane.defaults().top().growX().height(50f) 182 | this.settings = pane 183 | 184 | hover = Table(Tex.pane) 185 | hover!!.visible = false 186 | pane.addChild(hover) 187 | }.growX().fillY().top().scrollX(false).get() 188 | }.grow().maxWidth(1200f).pad(4f).margin(12f) 189 | cont.row() 190 | relaunchTip = cont.table(HeAssets.grayUIAlpha) { t -> 191 | t.add(Core.bundle["infos.requireRelaunch"]).color(Color.red) 192 | }.fill().center().margin(10f).pad(4f).get() 193 | relaunchTip.color.a(0f) 194 | 195 | rebuildSettings() 196 | } 197 | 198 | fun rebuildSettings() { 199 | if (currCat == null) { 200 | currCat = entries.orderedKeys().first() 201 | } 202 | 203 | settings!!.clearChildren() 204 | cfgCount = 0 205 | for (entry in entries[currCat]) { 206 | cfgCount++ 207 | settings!!.table((Tex.whiteui as TextureRegionDrawable).tint(Pal.darkestGray.cpy().a(0.5f*(cfgCount%2)))) { ent -> 208 | ent.clip = false 209 | ent.defaults().growY() 210 | entry.build(ent) 211 | }.name(entry.name) 212 | settings!!.row() 213 | } 214 | } 215 | 216 | fun requireRelaunch() { 217 | requireRelaunch = true 218 | relaunchTip.clearActions() 219 | relaunchTip.actions(Actions.alpha(1f, 0.5f)) 220 | } 221 | 222 | fun addConfig(category: String, vararg config: ConfigLayout?) { 223 | entries[category, { Seq() }].addAll(*config) 224 | if (category == currCat) rebuildSettings() 225 | } 226 | 227 | fun addConfig(category: String, icon: Drawable, vararg config: ConfigLayout) { 228 | entries[category, { Seq() }].addAll(*config) 229 | icons.put(category, icon) 230 | if (category == currCat) rebuildSettings() 231 | } 232 | 233 | fun removeCfg(category: String, name: String) { 234 | entries[category, nilSeq].remove { e -> e.name == name } 235 | if (category == currCat) rebuildSettings() 236 | } 237 | 238 | fun removeCat(category: String?) { 239 | entries.remove(category) 240 | icons.remove(category) 241 | } 242 | 243 | fun clearHover() { 244 | if (hover == null) return 245 | hover!!.clear() 246 | hover!!.visible = false 247 | } 248 | 249 | fun setHover(build: Cons) { 250 | if (hover == null) return 251 | 252 | clearHover() 253 | build[hover] 254 | } 255 | 256 | abstract class ConfigLayout(val name: String) { 257 | abstract fun build(table: Table) 258 | } 259 | 260 | companion object { 261 | private val nilSeq = Seq() 262 | 263 | var cfgCount: Int = 0 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/modpacker/MdtMetaGen.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs.modpacker 2 | 3 | import arc.util.serialization.Jval 4 | 5 | class MdtMetaGen : MetaGenerator { 6 | override fun genMeta(model: PackModel): String { 7 | val res: Jval = Jval.newObject() 8 | res.add("name", model.name) 9 | res.add("displayName", model.displayName) 10 | res.add("description", model.description) 11 | res.add("version", model.version) 12 | res.add("author", model.author) 13 | res.add("minGameVersion", model.minGameVersion) 14 | 15 | if (model.installMessage != null) res.add("installMessage", model.installMessage) 16 | 17 | res.add("deleteAtExit", Jval.valueOf(model.deleteAtExit)) 18 | res.add("disableOther", Jval.valueOf(model.disableOther)) 19 | res.add("shouldBackupData", Jval.valueOf(model.shouldBackupData)) 20 | res.add("shouldClearOldData", Jval.valueOf(model.shouldClearOldData)) 21 | 22 | res.add("hidden", Jval.valueOf(true)) 23 | 24 | val modList: Jval = Jval.newArray() 25 | for (mod in model.listMods()) { 26 | val modInfo: Jval = Jval.newObject() 27 | modInfo.add("name", mod.name) 28 | modInfo.add("version", mod.version) 29 | modInfo.add("author", mod.author) 30 | modInfo.add("file", mod.file!!.getName()) 31 | modList.add(modInfo) 32 | } 33 | res.add("mods", modList) 34 | 35 | res.add("main", "main.Main") 36 | 37 | return res.toString(Jval.Jformat.formatted) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/modpacker/MetaGenerator.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs.modpacker 2 | 3 | fun interface MetaGenerator { 4 | fun genMeta(model: PackModel): String 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/modpacker/ModInfo.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs.modpacker 2 | 3 | import mindustry.mod.Mods.LoadedMod 4 | import java.io.File 5 | import java.util.* 6 | 7 | class ModInfo { 8 | var name: String? = null 9 | var version: String? = null 10 | var author: String? = null 11 | var file: File? = null 12 | var dependencies = mutableListOf() 13 | 14 | override fun equals(o: Any?): Boolean { 15 | if (this === o) return true 16 | if (o == null || javaClass != o.javaClass) return false 17 | val modInfo = o as ModInfo 18 | return name == modInfo.name && version == modInfo.version && author == modInfo.author && file == modInfo.file && dependencies == modInfo.dependencies 19 | } 20 | 21 | override fun hashCode(): Int { 22 | return Objects.hash(name, version, author, file, dependencies) 23 | } 24 | 25 | companion object { 26 | private var map: HashMap? = null 27 | 28 | fun asLoaded(mod: LoadedMod?): ModInfo { 29 | if (map == null) { 30 | map = HashMap() 31 | } 32 | 33 | return map!!.computeIfAbsent(mod) { m: LoadedMod? -> 34 | val res = ModInfo() 35 | res.name = m!!.name 36 | res.version = m.meta.version 37 | res.author = m.meta.author 38 | res.dependencies = ArrayList(m.meta.dependencies.list()) 39 | 40 | res.file = m.file.file() 41 | res 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/dialogs/modpacker/PackModel.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.dialogs.modpacker 2 | 3 | import java.io.File 4 | 5 | 6 | class PackModel { 7 | var name: String = "" 8 | var displayName: String = "" 9 | var description: String = "" 10 | var version: String = "" 11 | var author: String = "" 12 | var minGameVersion: String = "0" 13 | 14 | var icon: File? = null 15 | var installMessage: String? = null 16 | 17 | var deleteAtExit: Boolean = false 18 | var disableOther: Boolean = false 19 | var shouldBackupData: Boolean = false 20 | var shouldClearOldData: Boolean = false 21 | 22 | var mods = HashSet() 23 | 24 | fun selected(mod: ModInfo): Boolean { 25 | return mods.contains(mod) 26 | } 27 | 28 | fun addMod(mod: ModInfo) { 29 | mods.add(mod) 30 | } 31 | 32 | fun removeMod(mod: ModInfo) { 33 | mods.remove(mod) 34 | } 35 | 36 | fun listMods(): MutableSet { 37 | return mods 38 | } 39 | 40 | open class Entry : Comparable { 41 | var fi: File? = null 42 | var to: String? = null 43 | 44 | override fun compareTo(entry: Entry): Int { 45 | return fi!!.getName().compareTo(entry.fi!!.getName()) 46 | } 47 | } 48 | 49 | fun check(): Int { 50 | val dependencies = HashMap() 51 | for (mod in mods) { 52 | for (dependence in mod.dependencies) { 53 | dependencies.putIfAbsent(dependence, false) 54 | } 55 | dependencies.put(mod.name, true) 56 | } 57 | 58 | for (value in dependencies.values) { 59 | if (!value) return 1 60 | } 61 | 62 | if (name.isEmpty() || version.isEmpty() || author.isEmpty() || displayName.isEmpty()) return 3 63 | 64 | return 0 65 | } 66 | 67 | fun genMeta(generator: MetaGenerator): String { 68 | return generator.genMeta(this) 69 | } 70 | 71 | companion object { 72 | fun getStateMessage(stateCode: Int): String? { 73 | return when (stateCode) { 74 | 1 -> "packer.requireDepend" 75 | 2 -> "packer.fileError" 76 | 3 -> "packer.metaMissing" 77 | else -> null 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/HeCollapser.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements 2 | 3 | import arc.func.Boolp 4 | import arc.func.Cons 5 | import arc.graphics.g2d.Draw 6 | import arc.math.Interp 7 | import arc.scene.actions.TemporalAction 8 | import arc.scene.event.Touchable 9 | import arc.scene.style.Drawable 10 | import arc.scene.ui.layout.Table 11 | import arc.scene.ui.layout.WidgetGroup 12 | import arc.util.ArcRuntimeException 13 | 14 | class HeCollapser( 15 | private var collTable: Table, 16 | var collapse: Boolean, 17 | private val collX: Boolean, 18 | private val collY: Boolean 19 | ) : WidgetGroup() { 20 | var collapsedFunc: Boolp? = null 21 | private val collapseAction = CollapseAction() 22 | var actionRunning = false 23 | var currentWidth = 0f 24 | var currentHeight = 0f 25 | 26 | constructor( 27 | collX: Boolean, 28 | collY: Boolean, 29 | collapsed: Boolean = false, 30 | background: Drawable? = null, 31 | cons: Cons
32 | ) : this(Table(background), collapsed, collX, collY) { 33 | cons.get(collTable) 34 | } 35 | 36 | init { 37 | isTransform = true 38 | 39 | updateTouchable() 40 | addChild(collTable) 41 | } 42 | 43 | fun setDuration(seconds: Float, interp: Interp = Interp.linear): HeCollapser { 44 | this.collapseAction.duration = seconds 45 | this.collapseAction.interpolation = interp 46 | return this 47 | } 48 | 49 | fun setCollapsed(collapsed: Boolp): HeCollapser { 50 | this.collapsedFunc = collapsed 51 | return this 52 | } 53 | 54 | fun toggle() { 55 | setCollapsed(!collapse) 56 | } 57 | 58 | fun setCollapsed(collapse: Boolean) { 59 | this.collapse = collapse 60 | updateTouchable() 61 | 62 | actionRunning = true 63 | 64 | addAction(collapseAction) 65 | collapseAction.restart() 66 | } 67 | 68 | private fun updateTouchable() { 69 | val touchable1 = if (collapse) Touchable.disabled else Touchable.enabled 70 | this.touchable = touchable1 71 | } 72 | 73 | override fun draw() { 74 | if (currentWidth > 1 && currentHeight > 1) { 75 | Draw.flush() 76 | if (clipBegin(x, y, currentWidth, currentHeight)) { 77 | super.draw() 78 | Draw.flush() 79 | clipEnd() 80 | } 81 | } 82 | } 83 | 84 | override fun drawChildren() { 85 | if (collapse && !actionRunning) return 86 | super.drawChildren() 87 | } 88 | 89 | override fun act(delta: Float) { 90 | super.act(delta) 91 | 92 | if (collapsedFunc != null) { 93 | val col = collapsedFunc!!.get() 94 | if (col != collapse) { 95 | setCollapsed(col) 96 | } 97 | } 98 | } 99 | 100 | override fun layout() { 101 | collTable.setBounds(0f, 0f, getWidth(), getHeight()) 102 | 103 | if (!actionRunning) { 104 | currentWidth = if (collX) if (collapse) 0f else collTable.prefWidth else getWidth() 105 | currentHeight = if (collY) if (collapse) 0f else collTable.prefHeight else getHeight() 106 | } 107 | } 108 | 109 | override fun getPrefWidth(): Float { 110 | if (!collX) return collTable.prefWidth 111 | 112 | if (!actionRunning) { 113 | return if (collapse) 0f 114 | else collTable.prefWidth 115 | } 116 | 117 | return currentWidth 118 | } 119 | 120 | override fun getPrefHeight(): Float { 121 | if (!collY) return collTable.prefHeight 122 | 123 | if (!actionRunning) { 124 | return if (collapse) 0f 125 | else collTable.prefHeight 126 | } 127 | 128 | return currentHeight 129 | } 130 | 131 | fun setTable(table: Table) { 132 | this.collTable = table 133 | clearChildren() 134 | addChild(table) 135 | } 136 | 137 | override fun getMinWidth(): Float { 138 | return 0f 139 | } 140 | 141 | override fun getMinHeight(): Float { 142 | return 0f 143 | } 144 | 145 | override fun childrenChanged() { 146 | super.childrenChanged() 147 | if (getChildren().size > 1) throw ArcRuntimeException("Only one actor can be added to CollapsibleWidget") 148 | } 149 | 150 | private inner class CollapseAction : TemporalAction() { 151 | override fun act(delta: Float) = super.act(delta) || !actionRunning 152 | 153 | override fun begin() { 154 | actionRunning = true 155 | } 156 | override fun update(percent: Float) { 157 | if (collX) { 158 | currentWidth = 159 | if (collapse) (1 - percent)*collTable.prefWidth 160 | else percent*collTable.prefWidth 161 | } 162 | if (collY) { 163 | currentHeight = 164 | if (collapse) (1 - percent)*collTable.prefHeight 165 | else percent*collTable.prefHeight 166 | } 167 | 168 | invalidateHierarchy() 169 | } 170 | 171 | override fun end() { 172 | actionRunning = false 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/SwappableCell.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements 2 | 3 | import arc.input.KeyCode 4 | import arc.math.geom.Rect 5 | import arc.math.geom.Vec2 6 | import arc.scene.Element 7 | import arc.scene.event.InputEvent 8 | import arc.scene.event.InputListener 9 | import arc.scene.event.Touchable 10 | import arc.scene.ui.layout.WidgetGroup 11 | import arc.struct.Seq 12 | import arc.util.Align 13 | 14 | private val v1 = Vec2() 15 | private val v2 = Vec2() 16 | private val v3 = Vec2() 17 | 18 | class SwappableCell( 19 | elem: T, 20 | val group: SwapGroup 21 | ): WidgetGroup(){ 22 | private val bound: Rect = Rect() 23 | 24 | private var moveX 25 | get() = element.translation.x 26 | set(value) { element.translation.x = value } 27 | private var moveY 28 | get() = element.translation.y 29 | set(value) { element.translation.y = value } 30 | 31 | var element: T = elem 32 | set(value) { 33 | removeChild(field) 34 | field = value 35 | addChild(value) 36 | } 37 | 38 | init { 39 | addChild(elem) 40 | touchable = Touchable.enabled 41 | setupListener() 42 | } 43 | 44 | override fun getPrefWidth() = element.prefWidth 45 | override fun getPrefHeight() = element.prefHeight 46 | 47 | override fun layout() { 48 | element.setBounds(0f, 0f, width, height) 49 | } 50 | 51 | fun get() = element 52 | 53 | private fun setupListener() { 54 | addListener(object : InputListener(){ 55 | var beginX = 0f 56 | var beginY = 0f 57 | var panned = false 58 | 59 | override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: KeyCode?): Boolean { 60 | if (panned || button != KeyCode.mouseLeft || pointer != 0) return false 61 | 62 | toFront() 63 | 64 | beginX = x 65 | beginY = y 66 | return true 67 | } 68 | 69 | override fun touchDragged(event: InputEvent?, x: Float, y: Float, pointer: Int) { 70 | moveX = x - beginX 71 | moveY = y - beginY 72 | panned = true 73 | 74 | super.touchDragged(event, x, y, pointer) 75 | } 76 | 77 | override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: KeyCode?) { 78 | if (!panned || button != KeyCode.mouseLeft || pointer != 0) return 79 | 80 | val other = checkOverlap(group) 81 | 82 | moveX = 0f 83 | moveY = 0f 84 | if (other != null) { 85 | val tmp = element 86 | element = other.element 87 | other.element = tmp 88 | 89 | validate() 90 | other.validate() 91 | } 92 | 93 | panned = false 94 | } 95 | }) 96 | } 97 | 98 | private fun absBound(): Rect { 99 | val botLeft = localToStageCoordinates(v1.setZero()) 100 | val topRight = localToStageCoordinates(v2.set(width, height)) 101 | 102 | return bound.set( 103 | botLeft.x, botLeft.y, 104 | topRight.x - botLeft.x, topRight.y - botLeft.y 105 | ) 106 | } 107 | 108 | private fun checkOverlap(group: SwapGroup): SwappableCell? { 109 | val centX = element.getX(Align.center) + moveX 110 | val centY = element.getY(Align.center) + moveY 111 | 112 | val stageV = localToStageCoordinates(v3.set(centX, centY)) 113 | 114 | group.cells.forEach { e -> 115 | val b = e.absBound() 116 | 117 | if (b.contains(stageV)) return e 118 | } 119 | 120 | return null 121 | } 122 | } 123 | 124 | class SwapGroup{ 125 | val cells = Seq>() 126 | 127 | fun swappable(elem: T) = SwappableCell(elem, this) 128 | .also { cells.add(it) } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/roulette/Roulette.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements.roulette 2 | 3 | import arc.scene.Element 4 | import arc.scene.ui.layout.WidgetGroup 5 | import arc.struct.Seq 6 | import helium.graphics.StripDrawable 7 | 8 | class Roulette( 9 | val background: StripDrawable? = null, 10 | ): WidgetGroup() { 11 | private val stripChildren = Seq() 12 | private val strips = Seq>() 13 | 14 | private var layers = 0 15 | private val layerCache = Seq>() 16 | 17 | fun add(elem: E): Strip where E : Element, E: StripElement{ 18 | val res = Strip(elem) 19 | 20 | addChild(elem) 21 | strips.add(res).sort { it.layer.toFloat() } 22 | res.layer = layers 23 | 24 | return res 25 | } 26 | 27 | fun newLayer(){ 28 | layers++ 29 | } 30 | 31 | override fun addChild(actor: Element) { 32 | if (actor is StripElement) stripChildren.add(actor) 33 | super.addChild(actor) 34 | } 35 | 36 | override fun removeChild(actor: Element, unfocus: Boolean): Boolean { 37 | if (actor is StripElement) stripChildren.remove(actor) 38 | return super.removeChild(actor, unfocus) 39 | } 40 | 41 | override fun draw() { 42 | drawShapeLines() 43 | super.draw() 44 | } 45 | 46 | override fun layout() { 47 | layerCache.clear() 48 | var currentLayer = 0 49 | var offsetDistance = 0 50 | 51 | var layerWidth = 0f 52 | var totalMinDelta = 0f 53 | strips.forEach { str -> 54 | if (str.layer != currentLayer) { 55 | layerCache.forEach { s -> 56 | totalMinDelta += s.element.minAngleDelta 57 | } 58 | 59 | layerCache.clear() 60 | currentLayer = str.layer 61 | } 62 | 63 | layerCache.add(str) 64 | } 65 | } 66 | 67 | private fun drawShapeLines() { 68 | background?.draw( 69 | x + width/2, y + width/2, 70 | width/2, 360f 71 | ) 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/roulette/Strip.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements.roulette 2 | 3 | open class Strip( 4 | val element: E 5 | ){ 6 | var layer = 0 7 | 8 | var minDelta = 0f 9 | var maxDelta = 0f 10 | var minWidth = 0f 11 | var maxWidth = 0f 12 | 13 | var padOffLeft = 0f 14 | var padOffRight = 0f 15 | var padOuter = 0f 16 | var padInner = 0f 17 | 18 | var fillDelta = false 19 | var fillWidth = false 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/roulette/StripButton.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements.roulette 2 | 3 | import arc.func.Boolp 4 | import arc.func.Cons 5 | import arc.scene.Element 6 | import arc.scene.event.* 7 | import arc.scene.ui.layout.Table 8 | import arc.scene.utils.Disableable 9 | import arc.util.pooling.Pools 10 | import helium.graphics.StripDrawable 11 | import helium.invoke 12 | 13 | open class StripButton( 14 | buttonStyle: StripButtonStyle, 15 | element: Element = Element(), 16 | ): StripWrap(element), Disableable { 17 | constructor(buttonStyle: StripButtonStyle, builder: Cons
): 18 | this(buttonStyle, Table().also { builder(it) }) 19 | 20 | var clickListener: ClickListener 21 | private set 22 | 23 | var style: StripButtonStyle = buttonStyle 24 | set(value) { 25 | field = value 26 | 27 | if (isDisable && value.disabled != null) background = value.disabled 28 | else if (isPressed && value.down != null) background = value.down 29 | else if (isChecked && value.checked != null) background = 30 | if (value.checkedOver != null && isOver) value.checkedOver else value.checked 31 | else if (isOver && value.over != null) { 32 | background = value.over 33 | } 34 | else if (value.up != null) background = value.up 35 | } 36 | 37 | private var disableProv: Boolp? = null 38 | 39 | var isDisable = false 40 | var isChecked = false 41 | val isPressed get() = clickListener.isVisualPressed 42 | val isOver get() = clickListener.isOver 43 | 44 | init { 45 | this.touchable = Touchable.enabled 46 | addListener(object : ClickListener() { 47 | override fun clicked(event: InputEvent?, x: Float, y: Float) { 48 | if (isDisable) return 49 | setChecked(!isChecked, true) 50 | } 51 | }.also { clickListener = it }) 52 | addListener(HandCursorListener()) 53 | } 54 | 55 | open fun setDisabled(disabled: Boolp) { 56 | this.disableProv = disabled 57 | } 58 | 59 | open fun setChecked(isChecked: Boolean, fireEvent: Boolean) { 60 | if (this.isChecked == isChecked) return 61 | this.isChecked = isChecked 62 | 63 | if (fireEvent) { 64 | val changeEvent = Pools.obtain(ChangeListener.ChangeEvent::class.java) { ChangeListener.ChangeEvent() } 65 | if (fire(changeEvent)) this.isChecked = !isChecked 66 | Pools.free(changeEvent) 67 | } 68 | } 69 | 70 | override fun act(delta: Float) { 71 | disableProv?.also { isDisable = it.get() } 72 | super.act(delta) 73 | } 74 | 75 | override fun draw() { 76 | validate() 77 | 78 | if (isDisable && style.disabled != null) background = style.disabled 79 | else if (isPressed && style.down != null) background = style.down 80 | else if (isChecked && style.checked != null) background = 81 | if (style.checkedOver != null && isOver) style.checkedOver else style.checked 82 | else if (isOver && style.over != null) { 83 | background = style.over 84 | } 85 | else if (style.up != null) background = style.up 86 | 87 | super.draw() 88 | } 89 | 90 | override fun isDisabled() = isDisable 91 | 92 | override fun setDisabled(isDisabled: Boolean) { 93 | this.isDisable = isDisabled 94 | } 95 | } 96 | 97 | class StripButtonStyle( 98 | val up: StripDrawable? = null, 99 | val down: StripDrawable? = null, 100 | val over: StripDrawable? = null, 101 | val checked: StripDrawable? = null, 102 | val checkedOver: StripDrawable? = null, 103 | val disabled: StripDrawable? = null, 104 | ){ 105 | constructor(other: StripButtonStyle): this( 106 | up = other.up, 107 | down = other.down, 108 | over = other.over, 109 | checked = other.checked, 110 | checkedOver = other.checkedOver, 111 | disabled = other.disabled 112 | ) 113 | 114 | fun copyWith( 115 | up: StripDrawable? = null, 116 | down: StripDrawable? = null, 117 | over: StripDrawable? = null, 118 | checked: StripDrawable? = null, 119 | checkedOver: StripDrawable? = null, 120 | disabled: StripDrawable? = null, 121 | ) = StripButtonStyle( 122 | up = up?:this.up, 123 | down = down?:this.down, 124 | over = over?:this.over, 125 | checked = checked?:this.checked, 126 | checkedOver = checkedOver?:this.checkedOver, 127 | disabled = disabled?:this.disabled, 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/roulette/StripElement.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements.roulette 2 | 3 | interface StripElement{ 4 | var centerX: Float 5 | var centerY: Float 6 | 7 | var angleDelta: Float 8 | var stripWidth: Float 9 | 10 | var angle: Float 11 | var distance: Float 12 | 13 | fun setRange(angleDelta: Float, stripWidth: Float): StripElement { 14 | this.angleDelta = angleDelta 15 | this.stripWidth = stripWidth 16 | return this 17 | } 18 | 19 | fun setCPosition(angle: Float, distance: Float): StripElement { 20 | this.angle = angle 21 | this.distance = distance 22 | return this 23 | } 24 | 25 | fun setCBounds(angle: Float, distance: Float, angleDelta: Float, stripWidth: Float): StripElement { 26 | this.angle = angle 27 | this.distance = distance 28 | this.angleDelta = angleDelta 29 | this.stripWidth = stripWidth 30 | return this 31 | } 32 | 33 | val minAngleDelta get() = prefAngleDelta 34 | val minStripWidth get() = prefStripWidth 35 | val prefAngleDelta get() = 0f 36 | val prefStripWidth get() = 0f 37 | val maxAngleDelta get() = 0f 38 | val maxStripWidth get() = 0f 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/elements/roulette/StripWrap.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.elements.roulette 2 | 3 | import arc.graphics.g2d.Draw 4 | import arc.math.Angles 5 | import arc.math.Mathf 6 | import arc.math.geom.Vec2 7 | import arc.scene.Element 8 | import arc.scene.event.Touchable 9 | import arc.scene.ui.layout.WidgetGroup 10 | import arc.util.Align 11 | import helium.graphics.StripDrawable 12 | 13 | open class StripWrap( 14 | elem: Element = Element(), 15 | var background: StripDrawable? = null 16 | ): WidgetGroup(), StripElement { 17 | companion object { 18 | val tmp: Vec2 = Vec2() 19 | } 20 | 21 | override var angleDelta = 0f 22 | override var stripWidth = 0f 23 | override var angle = 0f 24 | override var distance = 0f 25 | 26 | var element: Element = this 27 | set(value) { 28 | field.remove() 29 | addChild(value) 30 | field = value 31 | } 32 | 33 | init { 34 | element = elem 35 | } 36 | 37 | override fun draw() { 38 | drawBackground() 39 | super.draw() 40 | } 41 | 42 | open fun drawBackground() { 43 | Draw.color(color, color.a * parentAlpha) 44 | 45 | background?.draw( 46 | centerX, centerY, 47 | angle, distance, 48 | angleDelta, stripWidth, 49 | ) 50 | } 51 | 52 | override fun layout() { 53 | val centDelta = angleDelta/2 54 | val centWidth = distance + stripWidth/2 55 | 56 | val dx = Angles.trnsx(angle + centDelta, centWidth) 57 | val dy = Angles.trnsy(angle + centDelta, centWidth) 58 | 59 | element.pack() 60 | element.setPosition( 61 | width/2f + dx, 62 | height/2f + dy, 63 | Align.center 64 | ) 65 | } 66 | 67 | override fun hit(x: Float, y: Float, touchable: Boolean): Element? { 68 | if (touchable && this.touchable == Touchable.disabled) return null 69 | val point = tmp 70 | val childrenArray = children.items 71 | for (i in children.size - 1 downTo 0) { 72 | val child = childrenArray[i] 73 | if (!child.visible || (child.cullable && cullingArea != null && !cullingArea.overlaps( 74 | child.x + child.translation.x, 75 | child.y + child.translation.y, 76 | child.width, 77 | child.height 78 | )) 79 | ) continue 80 | child.parentToLocalCoordinates(point.set(x, y)) 81 | val hit = child.hit(point.x, point.y, touchable) 82 | if (hit != null) return hit 83 | } 84 | 85 | if (touchable && this.touchable != Touchable.enabled) return null 86 | 87 | val angle = Angles.angle(x - width/2, y - height/2) 88 | val dst = Mathf.dst(x - width/2, y - height/2) 89 | val halfAngle = this.angle + angleDelta/2 90 | 91 | return if (Angles.near(angle, halfAngle, angleDelta/2) && dst > distance && dst < distance + stripWidth) this 92 | else null 93 | } 94 | 95 | override var centerX: Float 96 | get() = getX(Align.center) 97 | set(value) { x = value - width/2f } 98 | override var centerY: Float 99 | get() = getY(Align.center) 100 | set(value) { y = value - height/2f } 101 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/ConfigurableDisplay.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo 2 | 3 | import arc.Core 4 | import arc.func.Boolp 5 | import arc.scene.style.Drawable 6 | import arc.scene.ui.layout.Table 7 | import arc.util.Align 8 | import arc.util.Scaling 9 | import kotlin.reflect.KMutableProperty0 10 | 11 | interface ConfigurableDisplay { 12 | fun getConfigures(): List 13 | } 14 | 15 | open class ConfigPair( 16 | val name: String, 17 | val icon: Drawable, 18 | val checked: Boolp? = null, 19 | val callback: Runnable 20 | ){ 21 | constructor( 22 | name: String, 23 | icon: Drawable, 24 | bind: KMutableProperty0 25 | ) : this( 26 | name, 27 | icon, 28 | { bind.get() }, 29 | { bind.set(!bind.get()) } 30 | ) 31 | 32 | open fun localized() = Core.bundle["config.$name"] 33 | open fun build(table: Table) { 34 | table.image(icon).size(38f).scaling(Scaling.fit) 35 | table.row() 36 | table.add(localized(), 0.8f) 37 | .width(100f).wrap().labelAlign(Align.center) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/EntityInfoDisplay.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo 2 | 3 | import arc.func.Prov 4 | import arc.math.geom.Rect 5 | import arc.scene.Element 6 | import arc.scene.ui.layout.Table 7 | import arc.struct.Bits 8 | import arc.util.pooling.Pool.Poolable 9 | import arc.util.pooling.Pools 10 | import mindustry.gen.Building 11 | import mindustry.gen.Drawc 12 | import mindustry.gen.Posc 13 | 14 | @Suppress("UNCHECKED_CAST") 15 | abstract class EntityInfoDisplay>( 16 | private val modelProv: Prov 17 | ) { 18 | private val modelType = modelProv.get()::class.java as Class 19 | 20 | fun obtainModel(ent: Posc): M = (Pools.obtain(modelType, modelProv) as Model) 21 | .also { 22 | it.entity = ent 23 | it.setup(ent) 24 | } as M 25 | 26 | abstract val layoutSide: Side 27 | open val hoveringOnly: Boolean get() = false 28 | open val screenRender: Boolean get() = true 29 | open val worldRender: Boolean get() = false 30 | 31 | open val maxSizeMultiple: Int get() = 6 32 | open val minSizeMultiple: Int get() = 2 33 | 34 | abstract fun valid(entity: Posc): Boolean 35 | abstract fun enabled(): Boolean 36 | 37 | abstract fun buildConfig(table: Table) 38 | 39 | open fun M?.checkHolding(isHold: Boolean, mouseHovering: Boolean) = isHold 40 | open fun M.checkWorldClip(worldViewport: Rect) = (entity as Posc).let { 41 | val clipSize = when(it){ 42 | is Drawc -> it.clipSize() 43 | is Building -> it.block.clipSize 44 | else -> 10f 45 | } 46 | worldViewport.overlaps(it.x - clipSize/2, it.y - clipSize/2, clipSize, clipSize) 47 | } 48 | open fun M.checkScreenClip(screenViewport: Rect, origX: Float, origY: Float, drawWidth: Float, drawHeight: Float) = 49 | screenViewport.overlaps( 50 | origX, origY, 51 | drawWidth, drawHeight 52 | ) 53 | open fun M.drawWorld(alpha: Float){} 54 | 55 | abstract val M.prefWidth: Float 56 | abstract val M.prefHeight: Float 57 | open fun M.shouldDisplay() = true 58 | abstract fun M.realWidth(prefSize: Float): Float 59 | abstract fun M.realHeight(prefSize: Float): Float 60 | abstract fun M.update(delta: Float) 61 | abstract fun M.draw(alpha: Float, scale: Float, origX: Float, origY: Float, drawWidth: Float, drawHeight: Float) 62 | } 63 | 64 | interface Model: Poolable{ 65 | var disabledTeam: Bits 66 | var entity: E 67 | fun setup(ent: E) 68 | } 69 | 70 | enum class Side(val dir: Int){ 71 | CENTER(-1), 72 | RIGHT(0), 73 | TOP(1), 74 | LEFT(2), 75 | BOTTOM(3) 76 | } 77 | 78 | abstract class NoneModelDisplay: EntityInfoDisplay>(modelProv = Prov { 79 | object : Model { 80 | override lateinit var disabledTeam: Bits 81 | override lateinit var entity: T 82 | override fun setup(ent: T) {} 83 | override fun reset() {} 84 | } 85 | }) 86 | 87 | abstract class WorldDrawOnlyDisplay>(modelProv: Prov): EntityInfoDisplay(modelProv) { 88 | override val layoutSide: Side get() = Side.CENTER 89 | override val M.prefWidth: Float get() = 0f 90 | override val M.prefHeight: Float get() = 0f 91 | override fun M.realWidth(prefSize: Float) = 0f 92 | override fun M.realHeight(prefSize: Float) = 0f 93 | override fun M.draw(alpha: Float, scale: Float, origX: Float, origY: Float, drawWidth: Float, drawHeight: Float) {} 94 | override fun M.drawWorld(alpha: Float) { 95 | draw(alpha) 96 | } 97 | abstract fun M.draw(alpha: Float) 98 | } 99 | 100 | interface InputCheckerModel: Model { 101 | var element: Element 102 | } 103 | 104 | interface InputEventChecker>{ 105 | fun T.buildListener(): Element 106 | } 107 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/EntityInfoStyles.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo 2 | 3 | class EntityInfoStyles { 4 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/displays/DetailsDisplay.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo.displays 2 | 3 | import arc.Core 4 | import arc.math.Interp 5 | import arc.math.Mathf 6 | import arc.math.geom.Rect 7 | import arc.scene.Element 8 | import arc.scene.ui.layout.Table 9 | import arc.struct.Bits 10 | import arc.util.Scaling 11 | import helium.ui.HeAssets 12 | import helium.ui.fragments.entityinfo.EntityInfoDisplay 13 | import helium.ui.fragments.entityinfo.InputCheckerModel 14 | import helium.ui.fragments.entityinfo.InputEventChecker 15 | import helium.ui.fragments.entityinfo.Side 16 | import helium.util.ifInst 17 | import mindustry.gen.Building 18 | import mindustry.gen.Icon 19 | import mindustry.gen.Posc 20 | import mindustry.gen.Teamc 21 | import mindustry.type.Category 22 | import mindustry.ui.Displayable 23 | import mindustry.ui.Styles 24 | 25 | class DetailsModel: InputCheckerModel { 26 | override lateinit var element: Element 27 | override lateinit var disabledTeam: Bits 28 | override lateinit var entity: Displayable 29 | 30 | var teamc: Teamc? = null 31 | 32 | var fadeOut = 0f 33 | var hovering = false 34 | var clipped = false 35 | 36 | override fun setup(ent: Displayable) { 37 | if (ent is Teamc) teamc = ent 38 | } 39 | override fun reset() { 40 | fadeOut = 0f 41 | hovering = false 42 | teamc = null 43 | } 44 | } 45 | 46 | class DetailsDisplay: EntityInfoDisplay(::DetailsModel), InputEventChecker { 47 | override val layoutSide: Side get() = Side.BOTTOM 48 | override val hoveringOnly: Boolean get() = true 49 | 50 | override val DetailsModel.prefWidth: Float 51 | get() = element.prefWidth 52 | override val DetailsModel.prefHeight: Float 53 | get() = element.prefHeight 54 | 55 | override val minSizeMultiple: Int get() = -1 56 | override val maxSizeMultiple: Int get() = -1 57 | 58 | override fun DetailsModel.buildListener(): Element { 59 | val tab = Table(HeAssets.padGrayUIAlpha).also { 60 | it.isTransform = true 61 | it.originX = 0f 62 | it.originY = 0f 63 | } 64 | entity.display(tab) 65 | 66 | entity.ifInst { build -> 67 | if ( 68 | (build.block.category == Category.distribution || build.block.category == Category.liquid) 69 | && build.block.displayFlow 70 | ){ 71 | tab.update { 72 | if (!hovering) { 73 | build.flowItems()?.stopFlow() 74 | build.liquids?.stopFlow() 75 | } 76 | else { 77 | build.flowItems()?.updateFlow() 78 | build.liquids?.updateFlow() 79 | } 80 | } 81 | } 82 | } 83 | 84 | tab.marginBottom(tab.background.bottomHeight) 85 | 86 | tab.visible { !clipped } 87 | return tab 88 | } 89 | 90 | override fun valid(entity: Posc) = entity is Displayable 91 | override fun enabled() = true 92 | 93 | override fun buildConfig(table: Table) { 94 | table.image(Icon.menu).size(64f).scaling(Scaling.fit) 95 | table.row() 96 | table.add(Core.bundle["infos.entityDetails"], Styles.outlineLabel) 97 | } 98 | 99 | override fun DetailsModel.checkScreenClip( 100 | screenViewport: Rect, 101 | origX: Float, 102 | origY: Float, 103 | drawWidth: Float, 104 | drawHeight: Float, 105 | ): Boolean { 106 | val res = screenViewport.overlaps( 107 | origX, origY, 108 | drawWidth, drawHeight 109 | ) 110 | clipped = res 111 | return res 112 | } 113 | 114 | override fun DetailsModel?.checkHolding(isHold: Boolean, mouseHovering: Boolean): Boolean { 115 | if (this != null) { 116 | val res = mouseHovering && teamc?.let { !disabledTeam.get(it.team().id) } ?: true 117 | if (res) { 118 | hovering = true 119 | return true 120 | } 121 | hovering = false 122 | return fadeOut > 0f 123 | } 124 | return mouseHovering 125 | } 126 | 127 | override fun DetailsModel.realWidth(prefSize: Float) = element.prefWidth 128 | override fun DetailsModel.realHeight(prefSize: Float) = element.prefHeight 129 | 130 | override fun DetailsModel.draw(alpha: Float, scale: Float, origX: Float, origY: Float, drawWidth: Float, drawHeight: Float) { 131 | val drawW = drawWidth/scale 132 | val drawH = drawHeight/scale 133 | val r = Interp.pow4Out.apply(fadeOut) 134 | element.scaleX = scale*r 135 | element.scaleY = scale 136 | element.setBounds(origX + drawWidth/2*(1 - r), origY, drawW, drawH) 137 | } 138 | 139 | override fun DetailsModel.update(delta: Float) { 140 | fadeOut = Mathf.approach(fadeOut, if (hovering) 1f else 0f, delta*0.06f) 141 | if (fadeOut <= 0f) element.visible = false 142 | else element.visible = true 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/displays/EntityRangeDisplay.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo.displays 2 | 3 | import arc.Core 4 | import arc.graphics.Color 5 | import arc.graphics.g2d.Draw 6 | import arc.graphics.g2d.Lines 7 | import arc.math.Interp 8 | import arc.math.Mathf 9 | import arc.math.geom.Rect 10 | import arc.scene.ui.layout.Table 11 | import arc.struct.Bits 12 | import arc.util.Scaling 13 | import arc.util.Time 14 | import arc.util.Tmp 15 | import helium.He 16 | import helium.graphics.DrawUtils 17 | import helium.graphics.HeShaders.entityRangeRenderer 18 | import helium.ui.fragments.entityinfo.ConfigPair 19 | import helium.ui.fragments.entityinfo.ConfigurableDisplay 20 | import helium.ui.fragments.entityinfo.Model 21 | import helium.ui.fragments.entityinfo.WorldDrawOnlyDisplay 22 | import mindustry.game.Team 23 | import mindustry.gen.Building 24 | import mindustry.gen.Icon 25 | import mindustry.gen.Posc 26 | import mindustry.gen.Unitc 27 | import mindustry.graphics.Layer 28 | import mindustry.graphics.Pal 29 | import mindustry.logic.Ranged 30 | import mindustry.ui.Styles 31 | import mindustry.world.blocks.defense.ForceProjector.ForceBuild 32 | import mindustry.world.blocks.defense.MendProjector.MendBuild 33 | import mindustry.world.blocks.defense.OverdriveProjector.OverdriveBuild 34 | import mindustry.world.blocks.defense.turrets.BaseTurret.BaseTurretBuild 35 | import mindustry.world.blocks.defense.turrets.Turret 36 | import mindustry.world.blocks.defense.turrets.Turret.TurretBuild 37 | import mindustry.world.blocks.units.RepairTower 38 | import mindustry.world.blocks.units.RepairTurret 39 | import mindustry.world.meta.BlockStatus 40 | 41 | class EntityRangeModel: Model { 42 | override lateinit var entity: Ranged 43 | override lateinit var disabledTeam: Bits 44 | 45 | var building: Building? = null 46 | var vis = 0f 47 | var range = 0f 48 | 49 | var hovering = false 50 | var isUnit = false 51 | var isTurret = false 52 | var isRepair = false 53 | var isOverdrive = false 54 | 55 | var timeOffset = 0f 56 | var phaseOffset = 0f 57 | var phaseScl = 0f 58 | 59 | val color = Color() 60 | var alpha = 0f 61 | 62 | var layerID = 0 63 | var layerOffset = 0f 64 | 65 | override fun setup(ent: Ranged) { 66 | timeOffset = Mathf.random(240f) 67 | phaseOffset = Mathf.random(360f) 68 | phaseScl = Mathf.random(0.9f, 1.1f) 69 | 70 | if (ent is Building) building = ent 71 | 72 | when(ent) { 73 | is Unitc -> isUnit = true 74 | is BaseTurretBuild -> isTurret = true 75 | is RepairTurret.RepairPointBuild -> isRepair = true 76 | is RepairTower.RepairTowerBuild -> isRepair = true 77 | is MendBuild -> isRepair = true 78 | is OverdriveBuild -> isOverdrive = true 79 | } 80 | 81 | layerID = when{ 82 | isUnit || isTurret -> { 83 | color.set(ent.team().color) 84 | alpha = 0.1f 85 | ent.team().id 86 | } 87 | isRepair -> { 88 | color.set(Pal.heal) 89 | alpha = 0.075f 90 | 260 91 | } 92 | isOverdrive -> { 93 | color.set(0.731f, 0.522f, 0.425f, 1f) 94 | alpha = 0.075f 95 | 261 96 | } 97 | else -> 300 98 | } 99 | layerOffset = layerID*0.01f 100 | } 101 | 102 | override fun reset() { 103 | hovering = false 104 | isUnit = false 105 | isTurret = false 106 | isRepair = false 107 | isOverdrive = false 108 | layerID = 0 109 | layerOffset = 0f 110 | color.set(Color.clear) 111 | alpha = 0f 112 | vis = 0f 113 | } 114 | } 115 | 116 | class EntityRangeDisplay: WorldDrawOnlyDisplay(::EntityRangeModel), ConfigurableDisplay { 117 | companion object { 118 | private var coneDrawing = false 119 | 120 | private val teamBits = Bits(Team.all.size) 121 | private var dashes = 0f 122 | 123 | fun resetMark(){ 124 | teamBits.clear() 125 | coneDrawing = false 126 | dashes = 0f 127 | } 128 | } 129 | 130 | override fun EntityRangeModel.shouldDisplay() = vis > 0 && He.config.let { 131 | ((isUnit || isTurret) && it.showAttackRange) 132 | || (isRepair && it.showHealRange) 133 | || (isOverdrive && it.showOverdriveRange) 134 | } 135 | 136 | override val worldRender: Boolean get() = true 137 | override val screenRender: Boolean get() = false 138 | 139 | override fun valid(entity: Posc): Boolean = entity is Ranged && entity !is ForceBuild 140 | override fun enabled() = He.config.let { 141 | it.enableRangeDisplay && (it.showAttackRange || it.showHealRange || it.showOverdriveRange) 142 | } 143 | 144 | override fun buildConfig(table: Table) { 145 | table.image(Icon.diagonal).size(64f).scaling(Scaling.fit) 146 | table.row() 147 | table.add(Core.bundle["infos.entityRange"], Styles.outlineLabel) 148 | } 149 | 150 | override fun getConfigures() = listOf( 151 | ConfigPair( 152 | "showAttackRange", 153 | Icon.turret, 154 | He.config::showAttackRange 155 | ), 156 | ConfigPair( 157 | "showHealRange", 158 | Icon.defense, 159 | He.config::showHealRange 160 | ), 161 | ConfigPair( 162 | "showOverdriveRange", 163 | Icon.upOpen, 164 | He.config::showOverdriveRange 165 | ) 166 | ) 167 | 168 | override fun EntityRangeModel.checkWorldClip(worldViewport: Rect) = (range*2).let { clipSize -> 169 | worldViewport.overlaps( 170 | entity.x - clipSize/2, entity.y - clipSize/2, 171 | clipSize, clipSize 172 | ) 173 | } 174 | 175 | override fun EntityRangeModel?.checkHolding(isHold: Boolean, mouseHovering: Boolean): Boolean { 176 | this?.hovering = isHold 177 | return isHold 178 | } 179 | 180 | override fun EntityRangeModel.draw(alpha: Float) { 181 | val a = (alpha/He.config.entityInfoAlpha*vis).let { if (it >= 0.999f) 1f else Interp.pow3Out.apply(it) } 182 | val radius = range*a 183 | val layer = Layer.light - 3 + layerOffset 184 | 185 | if (!teamBits.get(layerID)){ 186 | teamBits.set(layerID) 187 | Draw.drawRange(layer, 0.0045f, { 188 | entityRangeRenderer.capture() 189 | }) { 190 | entityRangeRenderer.alpha = this.alpha*He.config.entityInfoAlpha 191 | entityRangeRenderer.render() 192 | } 193 | } 194 | 195 | Draw.z(layer + 0.001f) 196 | Draw.color(Color.black, color, a) 197 | DrawUtils.fillCircle(entity.x, entity.y, radius - 1f) 198 | 199 | Draw.z(layer + 0.002f) 200 | val r = (Time.time*phaseScl + timeOffset)%240/240f 201 | val inner = Interp.pow3.apply(r) 202 | val outer = Interp.pow3Out.apply(r) 203 | 204 | DrawUtils.innerCircle( 205 | entity.x, entity.y, 206 | inner*radius, outer*radius, 207 | Tmp.c1.set(Color.white).a(0f), Color.white, 1 208 | ) 209 | 210 | Draw.z(layer + 0.003f) 211 | Lines.stroke(1f, Color.black) 212 | DrawUtils.lineCircle(entity.x, entity.y, radius + 1f) 213 | 214 | if (hovering) { 215 | Draw.z(Layer.light + 5) 216 | Draw.color(entity.team().color, 0.1f + Mathf.absin(8f, 0.15f)) 217 | if (isTurret && entity is TurretBuild) drawTurretAttackCone(entity as TurretBuild) 218 | else if (isUnit) drawUnitAttackCone(entity as Unitc) 219 | } 220 | } 221 | 222 | private fun drawUnitAttackCone(unit: Unitc) { 223 | unit.mounts().forEach { weapon -> 224 | val type = weapon.weapon 225 | val coneAngle = type.shootCone 226 | val weaponRot = if (weapon.rotate) weapon.rotation else type.baseRotation 227 | val dir = weaponRot + unit.rotation() 228 | val off = Tmp.v1.set(type.x, type.y).rotate(unit.rotation() - 90) 229 | off.add(Tmp.v2.set(type.shootX, type.shootY).rotate(unit.rotation() - 90 + weaponRot)) 230 | 231 | val dx = unit.x() + off.x 232 | val dy = unit.y() + off.y 233 | 234 | DrawUtils.circleFan( 235 | dx, dy, type.range(), 236 | coneAngle*2, dir - coneAngle 237 | ) 238 | } 239 | } 240 | 241 | private fun drawTurretAttackCone(turretBuild: TurretBuild) { 242 | val block = turretBuild.block as Turret 243 | val dir = turretBuild.buildRotation() 244 | val coneAngle = block.shootCone 245 | val offset = Tmp.v1.set(block.shootX, block.shootY).rotate(turretBuild.buildRotation() - 90) 246 | 247 | DrawUtils.circleFan( 248 | turretBuild.x() + offset.x, turretBuild.y() + offset.y, 249 | block.range, coneAngle*2, dir - coneAngle 250 | ) 251 | } 252 | 253 | override fun EntityRangeModel.update(delta: Float) { 254 | range = entity.range() 255 | val to = building?.let { 256 | if (it.status() != BlockStatus.noInput) 1f else 0f 257 | }?:1f 258 | if (!Mathf.equal(vis, to)) vis = Mathf.approach(vis, to, delta*0.04f) 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/ui/fragments/entityinfo/displays/StatusDisplay.kt: -------------------------------------------------------------------------------- 1 | package helium.ui.fragments.entityinfo.displays 2 | 3 | import arc.Core 4 | import arc.graphics.Color 5 | import arc.graphics.g2d.Draw 6 | import arc.math.Mathf 7 | import arc.scene.ui.layout.Scl 8 | import arc.scene.ui.layout.Table 9 | import arc.struct.Bits 10 | import arc.struct.Seq 11 | import arc.util.Align 12 | import arc.util.Scaling 13 | import arc.util.Tmp 14 | import helium.He 15 | import helium.ui.fragments.entityinfo.EntityInfoDisplay 16 | import helium.ui.fragments.entityinfo.Model 17 | import helium.ui.fragments.entityinfo.Side 18 | import mindustry.Vars 19 | import mindustry.gen.Icon 20 | import mindustry.gen.Posc 21 | import mindustry.gen.Statusc 22 | import mindustry.graphics.Pal 23 | import mindustry.type.StatusEffect 24 | import mindustry.ui.Fonts 25 | import mindustry.ui.Styles 26 | import kotlin.math.max 27 | 28 | private val iconSize = Scl.scl(26f) 29 | private val iconPadding = Scl.scl(4f) 30 | 31 | class StatusModel: Model{ 32 | override lateinit var entity: Statusc 33 | override lateinit var disabledTeam: Bits 34 | 35 | var singleWidth = 1f 36 | val statusList = Seq() 37 | 38 | override fun setup(ent: Statusc) { 39 | singleWidth = iconSize 40 | } 41 | 42 | override fun reset() { 43 | statusList.clear() 44 | singleWidth = iconSize 45 | } 46 | } 47 | 48 | class StatusDisplay: EntityInfoDisplay(::StatusModel) { 49 | override val layoutSide: Side = Side.TOP 50 | 51 | override val StatusModel.prefHeight: Float 52 | get() = iconSize 53 | override val StatusModel.prefWidth: Float 54 | get() = iconSize 55 | 56 | override fun valid(entity: Posc) = entity is Statusc 57 | override fun enabled() = He.config.enableUnitStatusDisplay 58 | 59 | override fun buildConfig(table: Table) { 60 | table.image(Icon.layers).size(64f).scaling(Scaling.fit) 61 | table.row() 62 | table.add(Core.bundle["infos.statusDisplay"], Styles.outlineLabel) 63 | } 64 | 65 | override fun StatusModel.shouldDisplay(): Boolean { 66 | return statusList.any() 67 | } 68 | 69 | override fun StatusModel.realHeight(prefSize: Float): Float{ 70 | val n = Mathf.ceil(prefSize/(singleWidth + iconPadding)) 71 | return n*iconSize 72 | } 73 | override fun StatusModel.realWidth(prefSize: Float) = prefSize 74 | 75 | override fun StatusModel.draw(alpha: Float, scale: Float, origX: Float, origY: Float, drawWidth: Float, drawHeight: Float) { 76 | var offX = 0f 77 | var offY = 0f 78 | 79 | val singW = singleWidth*scale 80 | 81 | var newSingleW = 0f 82 | 83 | var rh = iconSize 84 | statusList.forEach { eff -> 85 | if (offX + singW >= drawWidth) { 86 | offX = iconPadding 87 | offY += iconSize*scale 88 | rh += (iconSize + iconPadding)*scale 89 | } 90 | newSingleW = max(drawStatus(eff, alpha, scale, origX + offX, origY + offY), newSingleW) 91 | offX += singW + iconPadding*scale 92 | } 93 | 94 | singleWidth = newSingleW 95 | } 96 | 97 | private fun StatusModel.drawStatus( 98 | status: StatusEffect, 99 | alpha: Float, scale: Float, 100 | origX: Float, origY: Float, 101 | ): Float { 102 | val icon = status.uiIcon 103 | val iconHeight = iconSize*scale 104 | val iconWidth = iconSize*(icon.width.toFloat()/icon.height.toFloat())*scale 105 | 106 | var dw = iconWidth/scale 107 | 108 | Draw.color(Color.white, alpha) 109 | Draw.rect(icon, origX + iconWidth/2, origY + iconHeight/2, iconWidth, iconHeight) 110 | 111 | if (status.permanent) return dw 112 | val time = entity.getDuration(status) 113 | if (time > 0) { 114 | val timeWidth = Fonts.outline.draw( 115 | formatTime(time), 116 | origX, origY + Fonts.outline.lineHeight*scale*0.7f, 117 | Tmp.c1.set(Pal.accent).a(alpha), 118 | scale*0.7f, 119 | false, 120 | Align.bottomLeft 121 | ).width/scale 122 | 123 | dw = max(dw, timeWidth) 124 | } 125 | 126 | return dw 127 | } 128 | 129 | private fun formatTime(ticks: Float): String { 130 | val seconds = ticks/60f 131 | if (seconds > 60){ 132 | val minutes = seconds / 60 133 | if (minutes > 60){ 134 | val hours = minutes / 60 135 | return "${Mathf.round(hours)}h" 136 | } else { 137 | return "${Mathf.round(minutes)}m" 138 | } 139 | } 140 | else { 141 | return "${Mathf.round(seconds)}s" 142 | } 143 | } 144 | 145 | override fun StatusModel.update(delta: Float) { 146 | val list = statusList 147 | list.clear() 148 | Vars.content.statusEffects().forEach { eff -> 149 | if (!Core.atlas.isFound(eff.uiIcon)) return@forEach 150 | if (entity.hasEffect(eff)) { 151 | list.add(eff) 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/Downloader.kt: -------------------------------------------------------------------------------- 1 | package helium.util 2 | 3 | import arc.Core 4 | import arc.files.Fi 5 | import arc.func.Cons 6 | import arc.func.ConsT 7 | import arc.graphics.Pixmap 8 | import arc.graphics.Texture 9 | import arc.graphics.g2d.TextureRegion 10 | import arc.scene.style.Drawable 11 | import arc.scene.style.TextureRegionDrawable 12 | import arc.struct.OrderedMap 13 | import arc.util.Http 14 | import arc.util.Log 15 | import arc.util.io.Streams.OptimizedByteArrayOutputStream 16 | import java.io.OutputStream 17 | import kotlin.math.max 18 | 19 | object Downloader { 20 | private val urlReplacers = OrderedMap() 21 | 22 | fun setMirror(source: String, to: String) { 23 | urlReplacers.put(source, to) 24 | } 25 | 26 | fun removeMirror(source: String) { 27 | urlReplacers.remove(source) 28 | } 29 | 30 | fun clearMirrors() { 31 | urlReplacers.clear() 32 | } 33 | 34 | private fun retryDown( 35 | url: String, 36 | sync: Boolean, 37 | resultHandler: ConsT, 38 | maxRetry: Int, 39 | errHandler: Cons, 40 | ) { 41 | var url = url 42 | var counter = 0 43 | var get = {} 44 | 45 | for (entry in urlReplacers) { 46 | if (url.startsWith(entry.key!!)) { 47 | url = url.replaceFirst(entry.key!!.toRegex(), entry.value!!) 48 | } 49 | } 50 | 51 | val realUrl = url 52 | get = if (sync) {{ 53 | Http.get(realUrl).error{ e -> 54 | if (counter++ > maxRetry || e is InterruptedException) errHandler.get(e) 55 | else get() 56 | }.block(resultHandler) 57 | }} 58 | else {{ 59 | Http.get(realUrl, resultHandler){ e -> 60 | if (counter++ > maxRetry || e is InterruptedException) errHandler.get(e) 61 | else get() 62 | } 63 | }} 64 | get() 65 | } 66 | 67 | fun downloadToStream( 68 | url: String, 69 | stream: OutputStream, 70 | sync: Boolean = false, 71 | progressBack: Cons? = null, 72 | errHandler: Cons? = null, 73 | completed: Runnable? = null 74 | ) { 75 | retryDown(url, sync, { res -> 76 | stream.use { 77 | val input = res.resultAsStream 78 | val total = res.contentLength 79 | 80 | var curr = 0 81 | var b = input.read() 82 | while (b != -1) { 83 | if (Thread.interrupted()) 84 | throw InterruptedException() 85 | 86 | curr++ 87 | stream.write(b) 88 | progressBack?.get(curr.toFloat()/total) 89 | b = input.read() 90 | } 91 | 92 | completed?.run() 93 | } 94 | }, 5, { th -> errHandler?.get(th) }) 95 | } 96 | 97 | fun downloadToFile( 98 | url: String, 99 | file: Fi, 100 | sync: Boolean = false, 101 | progressBack: Cons? = null, 102 | errHandler: Cons? = null, 103 | completed: Runnable? = null 104 | ) { 105 | downloadToStream(url, file.write(), sync, progressBack, errHandler, completed) 106 | } 107 | 108 | fun downloadImg( 109 | url: String, 110 | errDef: TextureRegion, 111 | sync: Boolean = false, 112 | progressBack: Cons? = null, 113 | errHandler: Cons? = null, 114 | completed: Cons? = null, 115 | ): TextureRegion { 116 | val result = TextureRegion(errDef) 117 | 118 | doDownloadImg(url, sync, progressBack, result, completed, errHandler) 119 | 120 | return result 121 | } 122 | 123 | fun downloadLazyImg( 124 | url: String, 125 | errDef: TextureRegion, 126 | sync: Boolean = false, 127 | progressBack: Cons? = null, 128 | errHandler: Cons? = null, 129 | completed: Cons? = null, 130 | ): LazyRegionProv { 131 | val result = TextureRegion(errDef) 132 | 133 | return LazyRegionProv(result) { 134 | doDownloadImg(url, sync, progressBack, result, completed, errHandler) 135 | } 136 | } 137 | 138 | fun downloadLazyDrawable( 139 | url: String, 140 | errDef: TextureRegion, 141 | sync: Boolean = false, 142 | progressBack: Cons? = null, 143 | errHandler: Cons? = null, 144 | completed: Cons? = null, 145 | ): Drawable { 146 | val prov = downloadLazyImg(url, errDef, sync, progressBack, errHandler, completed) 147 | return object: TextureRegionDrawable(prov.region){ 148 | override fun draw(x: Float, y: Float, width: Float, height: Float) { 149 | prov.init() 150 | super.draw(x, y, width, height) 151 | } 152 | } 153 | } 154 | 155 | private fun doDownloadImg( 156 | url: String, 157 | sync: Boolean, 158 | progressBack: Cons?, 159 | result: TextureRegion, 160 | completed: Cons?, 161 | errHandler: Cons?, 162 | ) { 163 | retryDown(url, sync, { res -> 164 | val input = res.resultAsStream 165 | val total = res.contentLength 166 | val out = OptimizedByteArrayOutputStream(max(0, total).toInt()) 167 | 168 | out.use { stream -> 169 | var curr = 0 170 | var b = input.read() 171 | while (b != -1) { 172 | curr++ 173 | stream.write(b) 174 | progressBack?.get(curr.toFloat()/total) 175 | b = input.read() 176 | } 177 | } 178 | 179 | val pix = Pixmap(out.toByteArray()) 180 | Core.app.post { 181 | try { 182 | val tex = Texture(pix) 183 | tex.setFilter(Texture.TextureFilter.linear) 184 | result.set(tex) 185 | pix.dispose() 186 | 187 | completed?.get(result) 188 | } catch (e: Exception) { 189 | Log.err(e) 190 | } 191 | } 192 | }, 5, { th -> errHandler?.get(th) }) 193 | } 194 | } 195 | 196 | data class LazyRegionProv( 197 | val region: TextureRegion, 198 | val downloader: Runnable 199 | ){ 200 | private var done = false 201 | 202 | fun init(){ 203 | if (done) return 204 | downloader.run() 205 | done = true 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/Formatter.kt: -------------------------------------------------------------------------------- 1 | package helium.util 2 | 3 | import arc.util.Strings 4 | 5 | private val storeList = arrayOf( 6 | "B", "KB", "MB", "GB", 7 | "TB", "PB", "YB", "ZB" 8 | ) 9 | 10 | fun Float.toStoreSize(): String { 11 | var v = this 12 | var n = 0 13 | 14 | while (v > 1024) { 15 | v /= 1024 16 | n++ 17 | } 18 | 19 | return "${Strings.fixed(v, 2)}[lightgray]${storeList[n]}" 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/Geomtry.kt: -------------------------------------------------------------------------------- 1 | package helium.util 2 | 3 | import arc.math.geom.Vec2 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/Structs.kt: -------------------------------------------------------------------------------- 1 | package helium.util 2 | 3 | import kotlin.contracts.ExperimentalContracts 4 | import kotlin.contracts.InvocationKind 5 | import kotlin.contracts.contract 6 | import kotlin.reflect.KClass 7 | 8 | data class MutablePair( 9 | var first: A, 10 | var second: B 11 | ){ 12 | override fun toString(): String = "($first, $second)" 13 | } 14 | 15 | data class MutableTriple( 16 | var first: A, 17 | var second: B, 18 | var third: C 19 | ){ 20 | override fun toString(): String = "($first, $second, $third)" 21 | } 22 | 23 | infix fun A.mto(that: B): MutablePair = MutablePair(this, that) 24 | infix fun MutablePair.mto(that: C) = MutableTriple(this.first, this.second, that) 25 | 26 | @OptIn(ExperimentalContracts::class) 27 | inline fun Any.ifInst(block: (T) -> Unit){ 28 | contract { 29 | callsInPlace(block, InvocationKind.AT_MOST_ONCE) 30 | } 31 | if (this is T) block(this) 32 | } 33 | 34 | @OptIn(ExperimentalContracts::class) 35 | inline fun Any.runInst(clazz: KClass, block: (T) -> R): R?{ 36 | contract { 37 | callsInPlace(block, InvocationKind.AT_MOST_ONCE) 38 | } 39 | return if (this is T) block(this) else null 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/binds/CombineKeyListener.kt: -------------------------------------------------------------------------------- 1 | package helium.util.binds 2 | 3 | import arc.Core 4 | import arc.input.KeyCode 5 | import arc.scene.event.InputEvent 6 | import arc.scene.event.InputListener 7 | import arc.struct.Seq 8 | 9 | open class CombineKeyListener( 10 | val keysTree: CombineKeyTree, 11 | val fuzzed: Boolean = false 12 | ): InputListener(){ 13 | private val keysDown = Seq(KeyCode::class.java) 14 | 15 | override fun keyDown(event: InputEvent?, keycode: KeyCode?): Boolean { 16 | if (!keysTree.containsKeyCode(keycode)) return false 17 | keysDown.addUnique(keycode) 18 | 19 | if (keycode!!.isControllerKey()) return true 20 | 21 | if (!fuzzed) { 22 | val map = keysTree.getTargetBindings(Core.input) 23 | if (map.isEmpty()) return false 24 | 25 | val keys = CombinedKeys(*keysDown.toArray()) 26 | map[keys]?.let { keysDown(event, keycode, keys, it) } 27 | } 28 | else { 29 | keysTree.eachTargetBindings(Core.input, true){ k, r -> 30 | if (k.key == keycode) keysDown(event, keycode, k, r) 31 | } 32 | } 33 | 34 | return true 35 | } 36 | 37 | override fun keyUp(event: InputEvent?, keycode: KeyCode?): Boolean { 38 | if (!keysTree.containsKeyCode(keycode)) return false 39 | 40 | if (keycode!!.isControllerKey()){ 41 | return keysDown.remove(keycode) 42 | } 43 | 44 | if (!fuzzed) { 45 | val map = keysTree.getTargetBindings(Core.input) 46 | if (map.isEmpty()) return false 47 | 48 | keysDown.add(keycode) // 使按键抬起时必然监听到最后一个是抬起的按钮 49 | val keys = CombinedKeys(*keysDown.toArray()) 50 | map[keys]?.let { keysUp(event, keycode, keys, it) } 51 | keysDown.remove(keysDown.size - 1) 52 | } 53 | else { 54 | keysTree.eachTargetBindings(Core.input, true){ k, r -> 55 | if (k.key == keycode) keysUp(event, keycode, k, r) 56 | } 57 | } 58 | return keysDown.remove(keycode) 59 | } 60 | 61 | open fun keysDown(event: InputEvent?, keycode: KeyCode?, combinedKeys: CombinedKeys, rec: Rec) {} 62 | open fun keysUp(event: InputEvent?, keycode: KeyCode?, combinedKeys: CombinedKeys, rec: Rec) {} 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/binds/CombineKeyTree.kt: -------------------------------------------------------------------------------- 1 | package helium.util.binds 2 | 3 | import arc.Input 4 | import arc.func.Cons 5 | import arc.input.KeyCode 6 | 7 | class CombineKeyTree{ 8 | private val tempMap = mutableMapOf() 9 | 10 | private val normalBindings = mutableMapOf() 11 | private val ctrlBindings = mutableMapOf() 12 | private val altBindings = mutableMapOf() 13 | private val shiftBindings = mutableMapOf() 14 | private val altCtrlBindings = mutableMapOf() 15 | private val ctrlShiftBindings = mutableMapOf() 16 | private val altShiftBindings = mutableMapOf() 17 | private val altCtrlShiftBindings = mutableMapOf() 18 | 19 | fun putKeyBinding(binding: CombinedKeys, rec: Rec) { 20 | if (binding.isShift) { 21 | if (binding.isAlt && binding.isCtrl) altCtrlShiftBindings[binding] = rec 22 | else if (binding.isAlt) altShiftBindings[binding] = rec 23 | else if (binding.isCtrl) ctrlShiftBindings[binding] = rec 24 | else shiftBindings[binding] = rec 25 | } 26 | else { 27 | if (binding.isAlt && binding.isCtrl) altCtrlBindings[binding] = rec 28 | else if (binding.isAlt) altBindings[binding] = rec 29 | else if (binding.isCtrl) ctrlBindings[binding] = rec 30 | else normalBindings[binding] = rec 31 | } 32 | } 33 | 34 | fun putKeyBinds(vararg bindings: Pair) = bindings.forEach { putKeyBinding(it.first, it.second) } 35 | 36 | fun clear() { 37 | normalBindings.clear() 38 | altBindings.clear() 39 | ctrlBindings.clear() 40 | shiftBindings.clear() 41 | altShiftBindings.clear() 42 | ctrlShiftBindings.clear() 43 | altCtrlBindings.clear() 44 | altCtrlShiftBindings.clear() 45 | } 46 | 47 | fun forEach(block: (CombinedKeys, Rec) -> Unit) { 48 | normalBindings.forEach(block) 49 | ctrlBindings.forEach(block) 50 | altBindings.forEach(block) 51 | altCtrlBindings.forEach(block) 52 | ctrlShiftBindings.forEach(block) 53 | altShiftBindings.forEach(block) 54 | altCtrlShiftBindings.forEach(block) 55 | } 56 | 57 | fun containsKeyCode(key: KeyCode?): Boolean{ 58 | if (key == null) return false 59 | 60 | if (key.isCtrl()) 61 | return ctrlBindings.isNotEmpty() || ctrlShiftBindings.isNotEmpty() || altCtrlBindings.isNotEmpty() || altCtrlShiftBindings.isNotEmpty() 62 | if (key.isAlt()) 63 | return altBindings.isNotEmpty() || altShiftBindings.isNotEmpty() || altCtrlBindings.isNotEmpty() || altCtrlShiftBindings.isNotEmpty() 64 | if (key.isShift()) 65 | return shiftBindings.isNotEmpty() || ctrlShiftBindings.isNotEmpty() || altShiftBindings.isNotEmpty() || altCtrlShiftBindings.isNotEmpty() 66 | 67 | return normalBindings.any{ it.key.key == key } 68 | || ctrlBindings.any { it.key.key == key } 69 | || altBindings.any { it.key.key == key } 70 | || shiftBindings.any { it.key.key == key } 71 | || altCtrlBindings.any { it.key.key == key } 72 | || ctrlShiftBindings.any { it.key.key == key } 73 | || altShiftBindings.any { it.key.key == key } 74 | || altCtrlShiftBindings.any { it.key.key == key } 75 | } 76 | 77 | fun getTargetBindings(input: Input, fuzzyMatch: Boolean = false) = getTargetBindings( 78 | input.ctrl(), 79 | input.alt(), 80 | input.shift(), 81 | fuzzyMatch 82 | ) 83 | 84 | fun getTargetBindings(input: CombinedKeys, fuzzyMatch: Boolean = false) = getTargetBindings( 85 | input.isCtrl, 86 | input.isAlt, 87 | input.isShift, 88 | fuzzyMatch 89 | ) 90 | 91 | fun getTargetBindings( 92 | ctrlDown: Boolean, 93 | altDown: Boolean, 94 | shiftDown: Boolean, 95 | fuzzyMatch: Boolean = false 96 | ): Map{ 97 | tempMap.clear() 98 | if (fuzzyMatch){ 99 | tempMap.putAll(normalBindings) 100 | if (ctrlDown){ 101 | tempMap.putAll(ctrlBindings) 102 | if (altDown){ 103 | tempMap.putAll(altCtrlBindings) 104 | if (shiftDown) tempMap.putAll(altCtrlShiftBindings) 105 | } 106 | else if (shiftDown) tempMap.putAll(ctrlShiftBindings) 107 | } 108 | else { 109 | if (altDown){ 110 | tempMap.putAll(altBindings) 111 | if (shiftDown) tempMap.putAll(altShiftBindings) 112 | } 113 | else if (shiftDown) tempMap.putAll(shiftBindings) 114 | } 115 | return tempMap 116 | } 117 | else { 118 | return if (shiftDown) { 119 | if (altDown) if (ctrlDown) altCtrlShiftBindings else altShiftBindings 120 | else if (ctrlDown) ctrlShiftBindings else shiftBindings 121 | } 122 | else { 123 | if (altDown) if (ctrlDown) altCtrlBindings else altBindings 124 | else if (ctrlDown) ctrlBindings else normalBindings 125 | } 126 | } 127 | } 128 | 129 | inline fun eachTargetBindings(input: Input, fuzzyMatch: Boolean = false, cons: (CombinedKeys, Rec) -> Unit){ 130 | for ((k, r) in getTargetBindings(input, fuzzyMatch)) cons(k, r) 131 | } 132 | 133 | fun eachDown(input: Input, fuzzyMatch: Boolean = false, cons: Cons) { 134 | eachTargetBindings(input, fuzzyMatch){ k, r -> if (input.keyDown(k.key)) cons.get(r) } 135 | } 136 | 137 | fun eachRelease(input: Input, fuzzyMatch: Boolean = false, cons: Cons) { 138 | eachTargetBindings(input, fuzzyMatch){ k, r -> if (input.keyRelease(k.key)) cons.get(r) } 139 | } 140 | 141 | fun eachTap(input: Input, fuzzyMatch: Boolean = false, cons: Cons) { 142 | eachTargetBindings(input, fuzzyMatch){ k, r -> if (input.keyTap(k.key)) cons.get(r) } 143 | } 144 | 145 | fun checkDown(input: Input): Rec? { 146 | eachTargetBindings(input){ k, r -> if (input.keyDown(k.key)) return r } 147 | return null 148 | } 149 | 150 | fun checkRelease(input: Input): Rec? { 151 | eachTargetBindings(input){ k, r -> if (input.keyRelease(k.key)) return r } 152 | return null 153 | } 154 | 155 | fun checkTap(input: Input): Rec? { 156 | eachTargetBindings(input){ k, r -> if (input.keyTap(k.key)) return r } 157 | return null 158 | } 159 | 160 | override fun toString(): String { 161 | val stringBuilder = StringBuilder() 162 | forEach { keys, rec -> 163 | stringBuilder.append("$keys -> $rec,\n") 164 | } 165 | return stringBuilder.toString() 166 | } 167 | } -------------------------------------------------------------------------------- /src/main/kotlin/helium/util/binds/CombinedKeys.kt: -------------------------------------------------------------------------------- 1 | package helium.util.binds 2 | 3 | import arc.Input 4 | import arc.input.KeyCode 5 | import java.io.Serializable 6 | 7 | /**一个记录特定格式的标准组合键的标记对象 8 | * 9 | * 一个组合键由主键和控制键构成,其中控制键为`Ctrl`,*/ 10 | class CombinedKeys(vararg keys: KeyCode): Serializable { 11 | val isCtrl: Boolean 12 | val isAlt: Boolean 13 | val isShift: Boolean 14 | val key: KeyCode 15 | 16 | init { 17 | val ctrl = keys.filter { it.isCtrl() } 18 | val alt = keys.filter { it.isAlt() } 19 | val shift = keys.filter { it.isShift() } 20 | val normal = keys.filter { !ctrl.contains(it) && !alt.contains(it) && !shift.contains(it) } 21 | isCtrl = ctrl.isNotEmpty() 22 | isAlt = alt.isNotEmpty() 23 | isShift = shift.isNotEmpty() 24 | key = normal.lastOrNull()?:throw IllegalArgumentException("No key specified") 25 | } 26 | 27 | fun isDown(input: Input): Boolean{ 28 | if ((isCtrl && !input.ctrl()) || (!isCtrl && input.alt())) return false 29 | if ((isAlt && !input.alt()) || (!isAlt && input.alt())) return false 30 | if ((isShift && !input.shift()) || (!isShift && input.shift())) return false 31 | return input.keyDown(key) 32 | } 33 | 34 | fun isReleased(input: Input): Boolean{ 35 | if ((isCtrl && !input.ctrl()) || (!isCtrl && input.alt())) return false 36 | if ((isAlt && !input.alt()) || (!isAlt && input.alt())) return false 37 | if ((isShift && !input.shift()) || (!isShift && input.shift())) return false 38 | return input.keyRelease(key) 39 | } 40 | 41 | fun isTap(input: Input): Boolean{ 42 | if ((isCtrl && !input.ctrl()) || (!isCtrl && input.alt())) return false 43 | if ((isAlt && !input.alt()) || (!isAlt && input.alt())) return false 44 | if ((isShift && !input.shift()) || (!isShift && input.shift())) return false 45 | return input.keyTap(key) 46 | } 47 | 48 | override fun toString(): String { 49 | val builder = StringBuilder() 50 | 51 | if (isCtrl) builder.append("Ctrl").append(" + ") 52 | if (isAlt) builder.append("Alt").append(" + ") 53 | if (isShift) builder.append("Shift").append(" + ") 54 | 55 | builder.append(key.value) 56 | 57 | return builder.toString() 58 | } 59 | 60 | override fun hashCode(): Int { 61 | var res = key.hashCode() 62 | res = res*31 + isShift.hashCode() 63 | res = res*31 + isAlt.hashCode() 64 | res = res*31 + isCtrl.hashCode() 65 | return res 66 | } 67 | 68 | override fun equals(other: Any?): Boolean { 69 | return other is CombinedKeys 70 | && other.key == key 71 | && other.isCtrl == isCtrl 72 | && other.isAlt == isAlt 73 | && other.isShift == isShift 74 | } 75 | 76 | companion object { 77 | @JvmStatic 78 | fun toString(res: Iterable): String { 79 | val builder = StringBuilder() 80 | 81 | if (res.contains(KeyCode.controlLeft) || res.contains(KeyCode.controlRight)) builder.append("Ctrl").append(" + ") 82 | if (res.contains(KeyCode.altLeft) || res.contains(KeyCode.altRight)) builder.append("Alt").append(" + ") 83 | if (res.contains(KeyCode.shiftLeft) || res.contains(KeyCode.shiftRight)) builder.append("Shift").append(" + ") 84 | 85 | builder.append(res.lastOrNull { !it.isControllerKey() }?.value?:"") 86 | 87 | return builder.toString() 88 | } 89 | } 90 | } 91 | 92 | fun KeyCode.isControllerKey() 93 | = this == KeyCode.controlLeft || this == KeyCode.controlRight 94 | || this == KeyCode.altLeft || this == KeyCode.altRight 95 | || this == KeyCode.shiftLeft || this == KeyCode.shiftRight 96 | 97 | fun KeyCode.isCtrl() = this == KeyCode.controlLeft || this == KeyCode.controlRight 98 | fun KeyCode.isAlt() = this == KeyCode.altLeft || this == KeyCode.altRight 99 | fun KeyCode.isShift() = this == KeyCode.shiftLeft || this == KeyCode.shiftRight 100 | 101 | --------------------------------------------------------------------------------