├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── pack.mcmeta │ ├── assets │ │ └── naraechat │ │ │ └── lang │ │ │ ├── ko_kr.json │ │ │ └── en_us.json │ └── META-INF │ │ └── mods.toml │ └── java │ └── kr │ └── neko │ └── sokcuri │ └── naraechat │ ├── EasingFunctions.java │ ├── Wrapper │ ├── TextComponentWrapper.java │ ├── TextInputUtilWrapper.java │ └── TextFieldWidgetWrapper.java │ ├── Keyboard │ ├── KeyboardLayout.java │ ├── Hangul_Set_3_90_Layout.java │ ├── Hangul_Set_3_91_Layout.java │ ├── QwertyLayout.java │ └── Hangul_Set_2_Layout.java │ ├── Obfuscated │ ├── ReflectionFieldInfo.java │ ├── ObfuscatedMethod.java │ ├── ReflectionFieldMap.java │ └── ObfuscatedField.java │ ├── NaraeUtils.java │ ├── HangulProcessor.java │ ├── IMEIndicator.java │ └── NaraeChat.java ├── gradle.properties ├── .github ├── FUNDING.yml └── workflows │ └── build.yaml ├── CHANGELOG.md ├── README.md ├── gradlew.bat ├── .gitignore ├── gradlew └── LICENSE.txt /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sokcuri/NaraeChat/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "naraechat resources", 4 | "pack_format": 4 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/naraechat/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.naraechat.category": "나래챗 키 설정", 3 | "key.naraechat.ime_switch.desc": "한/영 전환키", 4 | "key.naraechat.hanja.desc": "한자 변환키", 5 | "options.naraechat.font": "폰트" 6 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | # This is required to provide enough memory for the Minecraft decompilation process. 3 | org.gradle.jvmargs=-Xmx3G 4 | org.gradle.daemon=false -------------------------------------------------------------------------------- /src/main/resources/assets/naraechat/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.naraechat.category": "NaraeChat Key Binding", 3 | "key.naraechat.ime_switch.desc": "KOR/ENG Toggle Key", 4 | "key.naraechat.hanja.desc": "Hanja Conversion Key", 5 | "options.naraechat.font": "Font" 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 25 16:00:30 KST 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/EasingFunctions.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat; 2 | 3 | public class EasingFunctions { 4 | public static float easeOutQuad(float t) { 5 | return t*(2-t); 6 | } 7 | public static float easeOutCubic(float t) { 8 | return (--t)*t*t+1; 9 | } 10 | public static float easeOutQuint(float t) { 11 | return 1+(--t)*t*t*t*t; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Wrapper/TextComponentWrapper.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Wrapper; 2 | 3 | public interface TextComponentWrapper { 4 | Object getBaseComponent(); 5 | 6 | int getCursorPosition(); 7 | void setCursorPosition(int pos); 8 | void deleteFromCursor(int num); 9 | 10 | String getText(); 11 | boolean setText(String str); 12 | 13 | void writeText(String str); 14 | 15 | void modifyText(char str); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Keyboard/KeyboardLayout.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Keyboard; 2 | 3 | import net.minecraftforge.client.event.GuiScreenEvent; 4 | import net.minecraftforge.event.TickEvent; 5 | 6 | import java.awt.*; 7 | 8 | public interface KeyboardLayout { 9 | String getName(); 10 | String getIndicatorText(); 11 | Color getIndicatorColor(); 12 | String getLayoutString(); 13 | 14 | void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event); 15 | void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event); 16 | void renderTick(TickEvent.RenderTickEvent event); 17 | 18 | void cleanUp(); 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: sokcuri 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.16.1-0.7.1 2 | - 책과 깃펜 등 특정 텍스트 필드에서 한글이 입력되지 않거나 마인크래프트가 크래시되던 문제 수정 3 | - Caps Lock이 켜져 있는 상태에서 입력시 쌍자음으로 입력되던 이슈 수정 4 | 5 | ## 1.16.1-0.7.0 6 | - 1.16.1 마인크래프트 대응 7 | - 유지보수 용이를 위해 각종 부가기능 (폰트 변경 기능 등) 삭제 8 | 9 | ## 0.6.0 10 | 폰트 변경기능을 추가했습니다 11 | - 마인크래프트 설정 > 언어에서 폰트 버튼을 눌러 폰트 변경 페이지로 이동할 수 있습니다 12 | - 기본값으로는 맑은 고딕이 지정됩니다 13 | - Over Sample을 0.0pt로 조정하면 기본 마인크래프트 폰트로 보여집니다 14 | 15 | ## 0.5.0 16 | 옵티파인 1.14.4_HD_U_F4_pre3 호환 17 | - 옵티파인과 함께 쓸 때 한글 채팅이 정상 작동하지 않는 문제 수정 18 | 표지판 입력기에서 간헐적으로 크래시가 발생하는 버그 수정 19 | 20 | ## 0.4.0 21 | 나래챗 모드 클라이언트에서 서버를 열면 다른 클라이언트 접속이 되지 않던 문제 수정 22 | 23 | ## 0.3.0 24 | 윈도우 화상 키보드에서 한/영 전환키 및 한자키 입력 가능하도록 수정 (#2) 25 | 26 | ## 0.2.0 27 | 일부 키보드 레이아웃에서 한/영 전환이 안되는 버그 수정 28 | 29 | ## 0.1.0 30 | 나래챗 릴리즈 -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[32,)" 3 | issueTrackerURL="https://github.com/sokcuri/naraechat/issues" 4 | license="GNU Lesser General Public License v3.0" 5 | [[mods]] 6 | modId="naraechat" 7 | version="${file.jarVersion}" 8 | displayName="Narae Chat" 9 | updateJSONURL="https://raw.githubusercontent.com/sokcuri/naraechat/master/data/update.json" 10 | displayURL="https://github.com/sokcuri/naraechat" 11 | authors="sokcuri" 12 | description="나래챗은 마인크래프트 한글 입력을 도와줍니다. by 소쿠릿" 13 | 14 | [[dependencies.naraechat]] # 15 | modId="forge" 16 | mandatory=true 17 | versionRange="[32,)" 18 | ordering="NONE" 19 | side="BOTH" 20 | [[dependencies.naraechat]] 21 | modId="minecraft" 22 | mandatory=true 23 | versionRange="[1.16,1.17)" 24 | ordering="NONE" 25 | side="BOTH" 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | if: "!contains(github.event.head_commit.message, '[skip-ci]')" 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Setup Java 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 8 17 | 18 | - name: Cache Gradle packages 19 | uses: actions/cache@v2 20 | with: 21 | path: ~/.gradle/caches 22 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 23 | restore-keys: ${{ runner.os }}-gradle 24 | 25 | - name: Build with Gradle 26 | run: chmod +x ./gradlew && ./gradlew build 27 | 28 | - name: Upload build artifact 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: artifact 32 | path: build/libs 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NaraeChat 2 | 나래챗은 마인크래프트에서 사용 가능한 한글 채팅 모드입니다. 한/영 전환 버튼을 사용해 한글 채팅을 할 수 있고 한글 입력 상태가 다른 키를 방해하지 않습니다 3 | 4 | ## 나래챗 정보 5 | * 만든이: [소쿠릿](https://twitter.com/sokcuri) 6 | * [홈페이지](https://github.com/sokcuri/NaraeChat) 7 | * [다운로드](https://github.com/sokcuri/NaraeChat/releases) 8 | * [업데이트 기록](CHANGELOG.md) 9 | * 버전: 1.16.1-0.7.1 (2020-07-19) 10 | * 마인크래프트 : 1.16.1 11 | * 포지 : 1.16.1-32.0.47 12 | * [라이센스 : LGPL 3.0](LICENSE.txt) 13 | * [버그 리포트 또는 개선 요청 올리기](https://github.com/sokcuri/NaraeChat/issues/new) 14 | 15 | ## 기능 16 | * 한영 키로 한글/영어 전환 가능 17 | * 한글 상태에서도 모든 키를 조작 가능 18 | * 자판 상태 인디케이터 기능 19 | 20 | ## 옵션 21 | * 인 게임 키 설정 란에서 한글/영어 전환 키를 변경할 수 있습니다 22 | 23 | ## 미구현 기능 24 | * 세벌식 키보드 자판 25 | * 한자 키 기능 26 | * 폰트 변경 기능 27 | 28 | ## 설치 방법 29 | * [Release 페이지](https://github.com/sokcuri/NaraeChat/releases)에서 jar 파일을 다운받고 마인크래프트 데이터 폴더(Windows는 %appdata%\\.minecraft, Linux는 ~/.minecraft) 아래에 있는 mods 폴더에 파일을 넣어주세요 30 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Obfuscated/ReflectionFieldInfo.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Obfuscated; 2 | 3 | public class ReflectionFieldInfo { 4 | static public ReflectionFieldInfo create(String name, Class type, Class owner, int depth) { 5 | return new ReflectionFieldInfo(name, type, owner, depth); 6 | } 7 | 8 | ReflectionFieldInfo(String name, Class type, Class owner, int depth) { 9 | this.name = name; 10 | this.type = type; 11 | this.owner = owner; 12 | this.depth = depth; 13 | } 14 | 15 | private String name; 16 | private Class type; 17 | private Class owner; 18 | private int depth; 19 | 20 | public String getName() { 21 | return this.name; 22 | } 23 | 24 | public Class getTypeClass() { 25 | return this.type; 26 | } 27 | 28 | public Class getOwnerClass() { 29 | return this.owner; 30 | } 31 | 32 | public int getDepth() { 33 | return this.depth; 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Keyboard/Hangul_Set_3_90_Layout.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Keyboard; 2 | 3 | import net.minecraftforge.client.event.GuiScreenEvent; 4 | import net.minecraftforge.event.TickEvent; 5 | 6 | import java.awt.*; 7 | 8 | public class Hangul_Set_3_90_Layout implements KeyboardLayout { 9 | private final String layout = "`ㅎㅆㅂㅛㅠㅑㅖㅢㅜㅋ-=~ㅈ@#$%^&*()_+ㅅㄹㅕㅐㅓㄹㄷㅁㅊㅍ[]\\ㅍㅌㅋㅒ;<789>{}|ㅇㄴㅣㅏㅡㄴㅇㄱㅈㅂㅌㄷㄶㄺㄲ/'456:\"ㅁㄱㅔㅗㅜㅅㅎ,.ㅗㅊㅄㄻㅀ!0123?"; 10 | 11 | @Override 12 | public String getName() { 13 | return "한글 3벌식 3-90"; 14 | } 15 | 16 | @Override 17 | public String getLayoutString() { 18 | return layout; 19 | } 20 | 21 | @Override 22 | public String getIndicatorText() { 23 | return "한글"; 24 | } 25 | 26 | @Override 27 | public Color getIndicatorColor() { 28 | return new Color(0x6C, 0x35, 0x8B); 29 | } 30 | 31 | @Override 32 | public void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 33 | 34 | } 35 | 36 | @Override 37 | public void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 38 | 39 | } 40 | 41 | @Override 42 | public void renderTick(TickEvent.RenderTickEvent event) { 43 | 44 | } 45 | 46 | @Override 47 | public void cleanUp() { 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Keyboard/Hangul_Set_3_91_Layout.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Keyboard; 2 | 3 | import net.minecraftforge.client.event.GuiScreenEvent; 4 | import net.minecraftforge.event.TickEvent; 5 | 6 | import java.awt.*; 7 | 8 | public class Hangul_Set_3_91_Layout implements KeyboardLayout { 9 | private final String layout = "*ㅎㅆㅂㅛㅠㅑㅖㅢㅜㅋ)>※ㄲㄺㅈㄿㄾ=“”'~;+ㅅㄹㅕㅐㅓㄹㄷㅁㅊㅍ(<:ㅍㅌㄵㅀㄽ56789%/\\ㅇㄴㅣㅏㅡㄴㅇㄱㅈㅂㅌㄷㄶㄼㄻㅒ01234·ㅁㄱㅔㅗㅜㅅㅎ,.ㅗㅊㅄㅋㄳ?-\",.!"; 10 | 11 | @Override 12 | public String getName() { 13 | return "한글 3벌식 최종"; 14 | } 15 | 16 | @Override 17 | public String getLayoutString() { 18 | return layout; 19 | } 20 | 21 | @Override 22 | public String getIndicatorText() { 23 | return "한글"; 24 | } 25 | 26 | @Override 27 | public Color getIndicatorColor() { 28 | return new Color(0x26, 0x68, 0x9A); 29 | } 30 | 31 | @Override 32 | public void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 33 | 34 | } 35 | 36 | @Override 37 | public void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 38 | 39 | } 40 | 41 | @Override 42 | public void renderTick(TickEvent.RenderTickEvent event) { 43 | 44 | } 45 | 46 | @Override 47 | public void cleanUp() { 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Keyboard/QwertyLayout.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Keyboard; 2 | 3 | import kr.neko.sokcuri.naraechat.IMEIndicator; 4 | import kr.neko.sokcuri.naraechat.NaraeUtils; 5 | import kr.neko.sokcuri.naraechat.Wrapper.TextComponentWrapper; 6 | import net.minecraftforge.client.event.GuiScreenEvent; 7 | import net.minecraftforge.event.TickEvent; 8 | 9 | import java.awt.*; 10 | 11 | public class QwertyLayout implements KeyboardLayout { 12 | private final String layout = "`1234567890-=~!@#$%^&*()_+qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"zxcvbnm,./ZXCVBNM<>?"; 13 | 14 | private static KeyboardLayout instance = new QwertyLayout(); 15 | public static KeyboardLayout getInstance() { 16 | return instance; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return "영문 쿼티"; 22 | } 23 | 24 | @Override 25 | public String getIndicatorText() { 26 | return "영문"; 27 | } 28 | 29 | @Override 30 | public Color getIndicatorColor() { 31 | return Color.YELLOW; 32 | } 33 | 34 | @Override 35 | public String getLayoutString() { 36 | return layout; 37 | } 38 | 39 | @Override 40 | public void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { } 41 | 42 | @Override 43 | public void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { } 44 | 45 | @Override 46 | public void renderTick(TickEvent.RenderTickEvent event) { 47 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 48 | if (comp == null) return; 49 | 50 | IMEIndicator.Instance.drawIMEIndicator(this); 51 | 52 | // Minecraft mc = Minecraft.getInstance(); 53 | // mc.fontRenderer.drawString(String.format("사용중인 자판 : %s", this.getName()), 176, 166, 16479127); 54 | } 55 | 56 | @Override 57 | public void cleanUp() { } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/NaraeUtils.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat; 2 | 3 | import kr.neko.sokcuri.naraechat.Obfuscated.ReflectionFieldInfo; 4 | import kr.neko.sokcuri.naraechat.Obfuscated.ReflectionFieldMap; 5 | import kr.neko.sokcuri.naraechat.Wrapper.TextComponentWrapper; 6 | import kr.neko.sokcuri.naraechat.Wrapper.TextFieldWidgetWrapper; 7 | import kr.neko.sokcuri.naraechat.Wrapper.TextInputUtilWrapper; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.gui.fonts.TextInputUtil; 10 | import net.minecraft.client.gui.widget.TextFieldWidget; 11 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | 18 | public class NaraeUtils { 19 | 20 | private static ReflectionFieldMap textFieldWidgetRefMap = new ReflectionFieldMap(TextFieldWidget.class); 21 | private static ReflectionFieldMap textInputUtilRefMap = new ReflectionFieldMap(TextInputUtil.class); 22 | 23 | public static TextComponentWrapper getTextComponent() { 24 | TextFieldWidget widget = getWidget(); 25 | TextInputUtil inputUtil = getTextInput(); 26 | 27 | if (widget != null) { 28 | return new TextFieldWidgetWrapper(widget); 29 | } 30 | 31 | if (inputUtil != null) { 32 | return new TextInputUtilWrapper(inputUtil); 33 | } 34 | return null; 35 | } 36 | 37 | private static TextInputUtil getTextInput() { 38 | Minecraft mc = Minecraft.getInstance(); 39 | if (mc.currentScreen == null) return null; 40 | 41 | return textInputUtilRefMap.findField(mc.currentScreen); 42 | } 43 | 44 | private static TextFieldWidget getWidget() { 45 | Minecraft mc = Minecraft.getInstance(); 46 | if (mc.currentScreen == null) return null; 47 | 48 | return textFieldWidgetRefMap.findField(mc.currentScreen); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Obfuscated/ObfuscatedMethod.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Obfuscated; 2 | 3 | import net.minecraft.client.gui.FontRenderer; 4 | import net.minecraft.client.gui.screen.inventory.CreativeScreen; 5 | import net.minecraft.client.gui.widget.TextFieldWidget; 6 | import net.minecraft.client.gui.widget.Widget; 7 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.Map; 11 | 12 | public final class ObfuscatedMethod { 13 | 14 | private static boolean isDeobf = false; 15 | 16 | private final String deobfName; 17 | private final String obfName; 18 | private final Class owner; 19 | private final Class retClass; 20 | private final Class[] parameters; 21 | 22 | public static class $CreativeScreen { 23 | public static final ObfuscatedMethod updateCreativeSearch; 24 | 25 | static { 26 | updateCreativeSearch = new ObfuscatedMethod("updateCreativeSearch", "func_147053_i", CreativeScreen.class, void.class); 27 | } 28 | } 29 | 30 | public ObfuscatedMethod(String deobfName, String obfName, Class owner, Class retClass, Class... parameters) { 31 | this.deobfName = deobfName; 32 | this.obfName = obfName; 33 | this.owner = owner; 34 | this.retClass = retClass; 35 | this.parameters = parameters; 36 | } 37 | 38 | public String getDeobfName() { 39 | return this.deobfName; 40 | } 41 | 42 | public String getObfName() { 43 | return this.obfName; 44 | } 45 | 46 | public Class getOwnerClass() { 47 | return this.owner; 48 | } 49 | 50 | public Class getReturnClass() { 51 | return this.retClass; 52 | } 53 | 54 | public Class[] getParameters() { 55 | return this.parameters; 56 | } 57 | 58 | public R invoke(O obj, Object... args) { 59 | try { 60 | return (R)ObfuscationReflectionHelper.findMethod(owner, isDeobf ? deobfName : obfName, parameters).invoke(obj, args); 61 | } catch (IllegalAccessException e) { 62 | e.printStackTrace(); 63 | } catch (InvocationTargetException e) { 64 | e.printStackTrace(); 65 | } 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Obfuscated/ReflectionFieldMap.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Obfuscated; 2 | 3 | import net.minecraft.client.gui.widget.TextFieldWidget; 4 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | 11 | public class ReflectionFieldMap { 12 | private HashMap> map = new HashMap<>(); 13 | private Class target; 14 | 15 | public ReflectionFieldMap(Class target) { 16 | this.target = target; 17 | } 18 | 19 | public List getReflectionArray(List array, Class owner, int depth) { 20 | for (Field field : owner.getDeclaredFields()) { 21 | array.add(ReflectionFieldInfo.create(field.getName(), field.getType(), owner, depth)); 22 | } 23 | 24 | if (owner.getSuperclass() != null) { 25 | getReflectionArray(array, owner.getSuperclass(), depth + 1); 26 | } 27 | return array; 28 | } 29 | 30 | public T findField(Object owner) { 31 | String className = owner.getClass().getName(); 32 | if (!map.containsKey(className)) { 33 | List fieldInfoArray = getReflectionArray(new ArrayList(), owner.getClass(), 0); 34 | List savedInfo = new ArrayList<>(); 35 | 36 | for (ReflectionFieldInfo fieldInfo : fieldInfoArray) { 37 | if (fieldInfo.getTypeClass() == target) { 38 | savedInfo.add(fieldInfo); 39 | } 40 | } 41 | 42 | map.put(className, savedInfo); 43 | } 44 | 45 | if (map.containsKey(className)) { 46 | for (ReflectionFieldInfo fieldInfo : map.get(className)) { 47 | Object obj = ObfuscationReflectionHelper.getPrivateValue(fieldInfo.getOwnerClass(), owner, fieldInfo.getName()); 48 | if (obj instanceof TextFieldWidget) { 49 | if (((TextFieldWidget)obj).isFocused()) { 50 | return (T)obj; 51 | } 52 | } else { 53 | return (T)obj; 54 | } 55 | } 56 | } 57 | return null; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/HangulProcessor.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat; 2 | 3 | import com.google.common.base.Splitter; 4 | 5 | import java.util.List; 6 | 7 | public class HangulProcessor { 8 | static String chosungTable = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"; 9 | static String jungsungTable = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"; 10 | static String jongsungTable = "\u0000ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"; 11 | static List jungsungCombiTable = Splitter.on(",").splitToList(",,,,,,,,,ㅗㅏ,ㅗㅐ,ㅗㅣ,,,ㅜㅓ,ㅜㅔ,ㅜㅣ,,,ㅡㅣ,ㅣ"); 12 | static List jongsungCombiTable = Splitter.on(",").splitToList(",,,ㄱㅅ,,ㄴㅈ,ㄴㅎ,,,ㄹㄱ,ㄹㅁ,ㄹㅂ,ㄹㅅ,ㄹㅌ,ㄹㅍ,ㄹㅎ,,,ㅂㅅ,,,,,,,,,"); 13 | 14 | public static boolean isJaeum(char c) { 15 | return c >= 0x3131 && c <= 0x314E; 16 | } 17 | 18 | public static boolean isMoeum(char c) { 19 | return c >= 0x314F && c <= 0x3163; 20 | } 21 | 22 | public static boolean isChosung(char c) { 23 | return getChosung(c) != -1; 24 | } 25 | 26 | public static boolean isJungsung(char c) { 27 | return getJungsung(c) != -1; 28 | } 29 | 30 | public static boolean isJungsung(char p, char c) { 31 | return getJungsung(p, c) != -1; 32 | } 33 | 34 | public static boolean isJongsung(char c) { 35 | return getJongsung(c) != -1; 36 | } 37 | 38 | public static boolean isJongsung(char p, char c) { 39 | return getJongsung(p, c) != -1; 40 | } 41 | 42 | public static int getChosung(char c) { 43 | return chosungTable.indexOf(c); 44 | } 45 | 46 | public static int getJungsung(char c) { 47 | return jungsungTable.indexOf(c); 48 | } 49 | 50 | public static int getJungsung(char p, char c) { 51 | int jung = ((p - 0xAC00) % (21 * 28)) / 28; 52 | 53 | for (int i = 0; i < jungsungCombiTable.size(); i++) { 54 | char[] tbl = jungsungCombiTable.get(i).toCharArray(); 55 | if (tbl.length == 2 && tbl[0] == jungsungTable.charAt(jung) && tbl[1] == c) { 56 | return i; 57 | } 58 | } 59 | return -1; 60 | } 61 | 62 | public static int getJongsung(char c) { 63 | return jongsungTable.indexOf(c); 64 | } 65 | 66 | public static int getJongsung(char p, char c) { 67 | int jong = ((p - 0xAC00) % (21 * 28)) % 28; 68 | 69 | for (int i = 0; i < jongsungCombiTable.size(); i++) { 70 | char[] tbl = jongsungCombiTable.get(i).toCharArray(); 71 | if (tbl.length == 2 && tbl[0] == jongsungTable.charAt(jong) && tbl[1] == c) { 72 | return i; 73 | } 74 | } 75 | return -1; 76 | } 77 | 78 | public static boolean isHangulSyllables(char c) { 79 | return c >= 0xAC00 && c <= 0xD7AF; 80 | } 81 | 82 | public static boolean isHangulCharacter(char c) { 83 | return isJaeum(c) || isMoeum(c) || isHangulSyllables(c); 84 | } 85 | 86 | public static char synthesizeHangulCharacter(int cho, int jung, int jong) { 87 | return (char)('가' + cho * 28 * 21 + jung * 28 + jong); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Wrapper/TextInputUtilWrapper.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Wrapper; 2 | 3 | import kr.neko.sokcuri.naraechat.Obfuscated.ObfuscatedField; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.FontRenderer; 6 | import net.minecraft.client.gui.fonts.TextInputUtil; 7 | 8 | import java.util.function.Consumer; 9 | import java.util.function.Predicate; 10 | import java.util.function.Supplier; 11 | 12 | public class TextInputUtilWrapper implements TextComponentWrapper { 13 | private final TextInputUtil base; 14 | 15 | public TextInputUtilWrapper(TextInputUtil inputUtil) { 16 | this.base = inputUtil; 17 | } 18 | 19 | public Minecraft getMinecraftInstance() { 20 | return Minecraft.getInstance(); 21 | } 22 | 23 | public FontRenderer getFontRenderer() { 24 | return Minecraft.getInstance().fontRenderer; 25 | } 26 | 27 | public Supplier getSupplier() { 28 | return ObfuscatedField.$TextInputUtil.textSupplier.get(base); 29 | } 30 | 31 | public Consumer getConsumer() { 32 | return ObfuscatedField.$TextInputUtil.textConsumer.get(base); 33 | } 34 | 35 | public Predicate getPredicate() { 36 | return ObfuscatedField.$TextInputUtil.textPredicate.get(base); 37 | } 38 | 39 | @Override 40 | public Object getBaseComponent() { 41 | return base; 42 | } 43 | 44 | @Override 45 | public int getCursorPosition() { 46 | return ObfuscatedField.$TextInputUtil.cursorPosition.get(base); 47 | } 48 | 49 | public void setCursorPosition(int pos) { 50 | ObfuscatedField.$TextInputUtil.cursorPosition.set(base, pos); 51 | } 52 | 53 | @Override 54 | public void deleteFromCursor(int num) { 55 | String text = getText(); 56 | if (!text.isEmpty()) { 57 | int cursorPosition = getCursorPosition(); 58 | boolean flag = num < 0; 59 | int i = flag ? cursorPosition + num : cursorPosition; 60 | int j = flag ? cursorPosition : cursorPosition + num; 61 | String s = ""; 62 | if (i >= 0) { 63 | s = text.substring(0, i); 64 | } 65 | 66 | if (j < text.length()) { 67 | s = s + text.substring(j); 68 | } 69 | 70 | setText(s); 71 | } 72 | } 73 | 74 | private int getCursorPosition2() { 75 | return ObfuscatedField.$TextInputUtil.cursorPosition2.get(base); 76 | } 77 | 78 | private void setCursorPosition2(int pos) { 79 | ObfuscatedField.$TextInputUtil.cursorPosition2.set(base, pos); 80 | } 81 | 82 | @Override 83 | public String getText() { 84 | return this.getSupplier().get(); 85 | } 86 | 87 | @Override 88 | public boolean setText(String str) { 89 | if (getPredicate().test(str)) { 90 | getConsumer().accept(str); 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | @Override 97 | public void writeText(String str) { 98 | int cursorPosition = getCursorPosition(); 99 | String s = getText(); 100 | String res; 101 | if (cursorPosition > 0) { 102 | res = s.substring(0, cursorPosition) + str + s.substring(cursorPosition); 103 | } else { 104 | res = str + s; 105 | } 106 | 107 | boolean textCommitted = setText(res); 108 | 109 | if (textCommitted && getText().length() == res.length()) { 110 | setCursorPosition(cursorPosition + 1); 111 | setCursorPosition2(cursorPosition + 1); 112 | } 113 | } 114 | 115 | @Override 116 | public void modifyText(char ch) { 117 | int cursorPosition = getCursorPosition(); 118 | char arr[] = getText().toCharArray(); 119 | if (cursorPosition > 0 && cursorPosition <= arr.length) { 120 | arr[cursorPosition - 1] = ch; 121 | setText(String.valueOf(arr)); 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,macos,gradle,windows,intellij,forgegradle 3 | # Edit at https://www.gitignore.io/?templates=java,macos,gradle,windows,intellij,forgegradle 4 | 5 | ### ForgeGradle ### 6 | # Minecraft client/server files 7 | run/ 8 | 9 | ### Intellij ### 10 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 11 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 12 | 13 | # User-specific stuff 14 | .idea/**/workspace.xml 15 | .idea/**/tasks.xml 16 | .idea/**/usage.statistics.xml 17 | .idea/**/dictionaries 18 | .idea/**/shelf 19 | 20 | # Generated files 21 | .idea/**/contentModel.xml 22 | 23 | # Sensitive or high-churn files 24 | .idea/**/dataSources/ 25 | .idea/**/dataSources.ids 26 | .idea/**/dataSources.local.xml 27 | .idea/**/sqlDataSources.xml 28 | .idea/**/dynamic.xml 29 | .idea/**/uiDesigner.xml 30 | .idea/**/dbnavigator.xml 31 | 32 | # Gradle 33 | .idea/**/gradle.xml 34 | .idea/**/libraries 35 | 36 | # Gradle and Maven with auto-import 37 | # When using Gradle or Maven with auto-import, you should exclude module files, 38 | # since they will be recreated, and may cause churn. Uncomment if using 39 | # auto-import. 40 | # .idea/modules.xml 41 | # .idea/*.iml 42 | # .idea/modules 43 | # *.iml 44 | # *.ipr 45 | 46 | # CMake 47 | cmake-build-*/ 48 | 49 | # Mongo Explorer plugin 50 | .idea/**/mongoSettings.xml 51 | 52 | # File-based project format 53 | *.iws 54 | 55 | # IntelliJ 56 | out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Cursive Clojure plugin 65 | .idea/replstate.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | # Editor-based Rest Client 74 | .idea/httpRequests 75 | 76 | # Android studio 3.1+ serialized cache file 77 | .idea/caches/build_file_checksums.ser 78 | 79 | ### Intellij Patch ### 80 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 81 | 82 | # *.iml 83 | # modules.xml 84 | # .idea/misc.xml 85 | # *.ipr 86 | 87 | # Sonarlint plugin 88 | .idea/sonarlint 89 | 90 | ### Java ### 91 | # Compiled class file 92 | *.class 93 | 94 | # Log file 95 | *.log 96 | 97 | # BlueJ files 98 | *.ctxt 99 | 100 | # Mobile Tools for Java (J2ME) 101 | .mtj.tmp/ 102 | 103 | # Package Files # 104 | *.jar 105 | *.war 106 | *.nar 107 | *.ear 108 | *.zip 109 | *.tar.gz 110 | *.rar 111 | 112 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 113 | hs_err_pid* 114 | 115 | ### macOS ### 116 | # General 117 | .DS_Store 118 | .AppleDouble 119 | .LSOverride 120 | 121 | # Icon must end with two \r 122 | Icon 123 | 124 | # Thumbnails 125 | ._* 126 | 127 | # Files that might appear in the root of a volume 128 | .DocumentRevisions-V100 129 | .fseventsd 130 | .Spotlight-V100 131 | .TemporaryItems 132 | .Trashes 133 | .VolumeIcon.icns 134 | .com.apple.timemachine.donotpresent 135 | 136 | # Directories potentially created on remote AFP share 137 | .AppleDB 138 | .AppleDesktop 139 | Network Trash Folder 140 | Temporary Items 141 | .apdisk 142 | 143 | ### Windows ### 144 | # Windows thumbnail cache files 145 | Thumbs.db 146 | Thumbs.db:encryptable 147 | ehthumbs.db 148 | ehthumbs_vista.db 149 | 150 | # Dump file 151 | *.stackdump 152 | 153 | # Folder config file 154 | [Dd]esktop.ini 155 | 156 | # Recycle Bin used on file shares 157 | $RECYCLE.BIN/ 158 | 159 | # Windows Installer files 160 | *.cab 161 | *.msi 162 | *.msix 163 | *.msm 164 | *.msp 165 | 166 | # Windows shortcuts 167 | *.lnk 168 | 169 | ### Gradle ### 170 | .gradle 171 | build/ 172 | 173 | # Ignore Gradle GUI config 174 | gradle-app.setting 175 | 176 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 177 | !gradle-wrapper.jar 178 | 179 | # Cache of project 180 | .gradletasknamecache 181 | 182 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 183 | # gradle/wrapper/gradle-wrapper.properties 184 | 185 | .idea 186 | 187 | ### Gradle Patch ### 188 | **/build/ 189 | 190 | # End of https://www.gitignore.io/api/java,macos,gradle,windows,intellij,forgegradle -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Wrapper/TextFieldWidgetWrapper.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Wrapper; 2 | 3 | import kr.neko.sokcuri.naraechat.Obfuscated.ObfuscatedField; 4 | import kr.neko.sokcuri.naraechat.Obfuscated.ObfuscatedMethod; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.screen.inventory.CreativeScreen; 7 | import net.minecraft.client.gui.widget.TextFieldWidget; 8 | 9 | import java.util.function.Consumer; 10 | 11 | public class TextFieldWidgetWrapper implements TextComponentWrapper { 12 | private final TextFieldWidget base; 13 | 14 | public TextFieldWidgetWrapper(TextFieldWidget widget) { 15 | this.base = widget; 16 | } 17 | 18 | private void sendTextChanged(String str) { 19 | if (getGuiResponder() != null) { 20 | getGuiResponder().accept(str); 21 | } 22 | } 23 | 24 | private void updateScreen() { 25 | Minecraft mc = Minecraft.getInstance(); 26 | if (mc.currentScreen == null) return; 27 | 28 | if (mc.currentScreen instanceof CreativeScreen) { 29 | CreativeScreen creativeScreen = (CreativeScreen)mc.currentScreen; 30 | ObfuscatedMethod.$CreativeScreen.updateCreativeSearch.invoke(creativeScreen); 31 | } 32 | } 33 | 34 | public int getSelectionEnd() { 35 | return ObfuscatedField.$TextFieldWidget.selectionEnd.get(base); 36 | } 37 | 38 | public int getLineScrollOffset() { 39 | return ObfuscatedField.$TextFieldWidget.lineScrollOffset.get(base); 40 | } 41 | 42 | public boolean isEnabled() { 43 | return ObfuscatedField.$TextFieldWidget.isEnabled.get(base); 44 | } 45 | 46 | public boolean getEnableBackgroundDrawing() { 47 | return ObfuscatedField.$TextFieldWidget.enableBackgroundDrawing.get(base); 48 | } 49 | 50 | public Consumer getGuiResponder() { 51 | return ObfuscatedField.$TextFieldWidget.guiResponder.get(base); 52 | } 53 | 54 | public int getWidth() { 55 | return base.getWidth(); 56 | } 57 | 58 | public boolean isFocused() { 59 | return base.isFocused(); 60 | } 61 | 62 | public int getX() { 63 | return base.x; 64 | } 65 | 66 | public int getY() { 67 | return base.y; 68 | } 69 | 70 | public int getHeight() { 71 | return base.getHeightRealms(); 72 | } 73 | 74 | public int getAdjustedWidth() { 75 | return base.getAdjustedWidth(); 76 | } 77 | 78 | public int getCursorPosition() { 79 | return base.getCursorPosition(); 80 | } 81 | 82 | @Override 83 | public void setCursorPosition(int pos) { 84 | base.setCursorPosition(pos); 85 | } 86 | 87 | public int getMaxStringLength() { 88 | return ObfuscatedField.$TextFieldWidget.maxStringLength.get(base); 89 | } 90 | 91 | public int getNthWordFromCursor(int numWords) { 92 | return base.getNthWordFromCursor(numWords); 93 | } 94 | 95 | public String getSelectedText() { 96 | return base.getSelectedText(); 97 | } 98 | 99 | public boolean getCanLoseFocus() { 100 | return ObfuscatedField.$TextFieldWidget.canLoseFocus.get(base); 101 | } 102 | public void setCanLoseFocus(boolean b) { 103 | base.setCanLoseFocus(b); 104 | } 105 | 106 | public String getText() { 107 | return base.getText(); 108 | } 109 | 110 | public boolean setText(String str) { 111 | base.setText(str); 112 | sendTextChanged(str); 113 | updateScreen(); 114 | return true; 115 | } 116 | 117 | public void deleteFromCursor(int num) { 118 | base.deleteFromCursor(num); 119 | } 120 | 121 | public boolean getVisible() { 122 | return base.getVisible(); 123 | } 124 | 125 | @Override 126 | public Object getBaseComponent() { 127 | return base; 128 | } 129 | 130 | @Override 131 | public void writeText(String str) { 132 | base.writeText(str); 133 | sendTextChanged(str); 134 | updateScreen(); 135 | } 136 | 137 | @Override 138 | public void modifyText(char ch) { 139 | int cursorPosition = getCursorPosition(); 140 | setCursorPosition(cursorPosition - 1); 141 | deleteFromCursor(1); 142 | writeText(String.valueOf(Character.toChars(ch))); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Obfuscated/ObfuscatedField.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Obfuscated; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.gui.FontRenderer; 5 | import net.minecraft.client.gui.fonts.TextInputUtil; 6 | import net.minecraft.client.gui.widget.TextFieldWidget; 7 | import net.minecraft.client.gui.widget.Widget; 8 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 9 | 10 | import java.util.function.Consumer; 11 | import java.util.function.Predicate; 12 | import java.util.function.Supplier; 13 | 14 | public final class ObfuscatedField { 15 | 16 | private static boolean isDeobf = false; 17 | 18 | private final String obfName; 19 | private final String deobfName; 20 | private final Class owner; 21 | private final Class retClass; 22 | 23 | public static class $TextFieldWidget { 24 | public static final ObfuscatedField canLoseFocus; 25 | public static final ObfuscatedField enableBackgroundDrawing; 26 | public static final ObfuscatedField> guiResponder; 27 | public static final ObfuscatedField isEnabled; 28 | public static final ObfuscatedField lineScrollOffset; 29 | public static final ObfuscatedField maxStringLength; 30 | public static final ObfuscatedField selectionEnd; 31 | 32 | static { 33 | canLoseFocus = new ObfuscatedField("canLoseFocus", "field_146212_n", TextFieldWidget.class, boolean.class); 34 | enableBackgroundDrawing = new ObfuscatedField("enableBackgroundDrawing", "field_146215_m", TextFieldWidget.class, boolean.class); 35 | guiResponder = new ObfuscatedField("guiResponder", "field_175210_x", TextFieldWidget.class, Consumer.class); 36 | isEnabled = new ObfuscatedField("isEnabled", "field_146226_p", TextFieldWidget.class, boolean.class); 37 | lineScrollOffset = new ObfuscatedField("lineScrollOffset", "field_146225_q", TextFieldWidget.class, int.class); 38 | maxStringLength = new ObfuscatedField("maxStringLength", "field_146217_k", TextFieldWidget.class, int.class); 39 | selectionEnd = new ObfuscatedField("selectionEnd", "field_146223_s", TextFieldWidget.class, int.class); 40 | } 41 | } 42 | 43 | public static class $TextInputUtil { 44 | public static final ObfuscatedField> textSupplier; 45 | public static final ObfuscatedField> textConsumer; 46 | public static final ObfuscatedField> textPredicate; 47 | public static final ObfuscatedField maxStringLength; 48 | public static final ObfuscatedField cursorPosition; 49 | public static final ObfuscatedField cursorPosition2; 50 | 51 | static { 52 | textSupplier = new ObfuscatedField("textConsumer", "field_216902_c", TextInputUtil.class, Supplier.class); 53 | textConsumer = new ObfuscatedField("textSupplier", "field_216903_d", TextInputUtil.class, Consumer.class); 54 | textPredicate = new ObfuscatedField("textPredicate", "field_238566_e_", TextInputUtil.class, Consumer.class); 55 | maxStringLength = new ObfuscatedField("field_216906_g", "field_216906_g", TextInputUtil.class, int.class); 56 | cursorPosition = new ObfuscatedField("field_216905_f", "field_216905_f", TextInputUtil.class, int.class); 57 | cursorPosition2 = new ObfuscatedField("field_216906_g", "field_216906_g", TextInputUtil.class, int.class); 58 | } 59 | } 60 | 61 | public static class $Widget { 62 | public static final ObfuscatedField focused; 63 | 64 | static { 65 | focused = new ObfuscatedField("focused", "field_230686_c_", Widget.class, boolean.class); 66 | } 67 | } 68 | 69 | public ObfuscatedField(String deobfName, String obfName, Class owner, Class retClass) { 70 | this.obfName = obfName; 71 | this.deobfName = deobfName; 72 | this.owner = owner; 73 | this.retClass = retClass; 74 | } 75 | 76 | public String getDeobfName() { 77 | return this.deobfName; 78 | } 79 | 80 | public String getObjName() { 81 | return this.obfName; 82 | } 83 | 84 | public Class getOwnerClass() { 85 | return this.owner; 86 | } 87 | 88 | public Class getTypeClass() { 89 | return this.retClass; 90 | } 91 | 92 | public T get(O obj) { 93 | return (T)ObfuscationReflectionHelper.getPrivateValue(owner, obj, isDeobf ? deobfName : obfName); 94 | } 95 | 96 | public void set(O obj, T value) { 97 | ObfuscationReflectionHelper.setPrivateValue(owner, obj, value, isDeobf ? deobfName : obfName); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/IMEIndicator.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat; 2 | 3 | import com.mojang.blaze3d.matrix.MatrixStack; 4 | import com.mojang.blaze3d.platform.GlStateManager; 5 | import kr.neko.sokcuri.naraechat.Keyboard.KeyboardLayout; 6 | import kr.neko.sokcuri.naraechat.Wrapper.TextComponentWrapper; 7 | import kr.neko.sokcuri.naraechat.Wrapper.TextFieldWidgetWrapper; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.gui.FontRenderer; 10 | import net.minecraft.client.renderer.BufferBuilder; 11 | import net.minecraft.client.renderer.Tessellator; 12 | import net.minecraft.client.renderer.vertex.DefaultVertexFormats; 13 | import org.lwjgl.opengl.GL11; 14 | 15 | import java.awt.*; 16 | 17 | import static org.lwjgl.glfw.GLFW.glfwGetTime; 18 | import static org.lwjgl.opengl.GL11.GL_FILL; 19 | import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK; 20 | 21 | public class IMEIndicator { 22 | public static IMEIndicator Instance = new IMEIndicator(); 23 | 24 | static int savedTextLength = 0; 25 | static float savedIndicatorX = 0; 26 | static float currentIndicatorX = 0; 27 | static float animationTickTime = 0; 28 | static Object targetComponent; 29 | 30 | private IMEIndicator() { } 31 | 32 | public void reset() { 33 | savedTextLength = 0; 34 | savedIndicatorX = 0; 35 | currentIndicatorX = 0; 36 | animationTickTime = 0; 37 | } 38 | 39 | public void drawIMEIndicator(KeyboardLayout layout) { 40 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 41 | 42 | if (!(comp instanceof TextFieldWidgetWrapper)) { 43 | return; 44 | } 45 | 46 | TextFieldWidgetWrapper wrapper = (TextFieldWidgetWrapper)comp; 47 | FontRenderer fontRenderer = Minecraft.getInstance().fontRenderer; 48 | 49 | boolean enableBackgroundDrawing = wrapper.getEnableBackgroundDrawing(); 50 | boolean isEnabled = wrapper.isEnabled(); 51 | 52 | int width = wrapper.getWidth(); 53 | int height = wrapper.getHeight(); 54 | 55 | boolean focused = wrapper.isFocused(); 56 | 57 | if (!isEnabled || !focused) { 58 | return; 59 | } 60 | 61 | int x = enableBackgroundDrawing ? wrapper.getX() + 4 : wrapper.getX(); 62 | int y = enableBackgroundDrawing ? wrapper.getY() + (height - 8) / 2 : wrapper.getY(); 63 | 64 | String text = wrapper.getText(); 65 | 66 | float indicatorX = x; 67 | String indicatorFirst = layout.getIndicatorText(); 68 | String indicatorLast = String.format("[%d]", text.length()); 69 | String indicatorStr = indicatorFirst + indicatorLast; 70 | 71 | int indicatorMargin = 1; 72 | int indicatorFirstWidth = fontRenderer.getStringWidth(indicatorFirst); 73 | int indicatorWidth = fontRenderer.getStringWidth(indicatorStr); 74 | int indicatorHeight = fontRenderer.getWordWrappedHeight(indicatorStr, 100); 75 | 76 | int strWidth = fontRenderer.getStringWidth(text); 77 | if (strWidth + indicatorWidth > width) { 78 | indicatorX = x + width - indicatorWidth; 79 | } else { 80 | indicatorX += strWidth; 81 | } 82 | 83 | if (targetComponent == null || targetComponent != comp.getBaseComponent()) { 84 | targetComponent = comp.getBaseComponent(); 85 | savedTextLength = 0; 86 | savedIndicatorX = indicatorX; 87 | currentIndicatorX = indicatorX; 88 | animationTickTime = 0; 89 | } 90 | 91 | if (text.length() != savedTextLength) { 92 | animationTickTime = (float)glfwGetTime(); 93 | savedTextLength = text.length(); 94 | savedIndicatorX = currentIndicatorX; 95 | } 96 | 97 | if (glfwGetTime() - animationTickTime > 1.0f) { 98 | savedIndicatorX = indicatorX; 99 | } else { 100 | currentIndicatorX = savedIndicatorX + (indicatorX - savedIndicatorX) * EasingFunctions.easeOutQuint((float)glfwGetTime() - animationTickTime); 101 | indicatorX = currentIndicatorX; 102 | } 103 | 104 | drawIndicatorBox(indicatorX - indicatorMargin, y - height - indicatorMargin, indicatorX + indicatorWidth + indicatorMargin, y - height + indicatorHeight + indicatorMargin); 105 | fontRenderer.drawString(new MatrixStack(), indicatorFirst, indicatorX, y - height, layout.getIndicatorColor().getRGB()); 106 | fontRenderer.drawString(new MatrixStack(), indicatorLast, indicatorX + indicatorFirstWidth, y - height, new Color(0xFF, 0xFF, 0xFF).getRGB()); 107 | } 108 | 109 | void drawIndicatorBox(float x, float y, float cx, float cy) { 110 | if (x < cx) { 111 | float i = x; 112 | x = cx; 113 | cx = i; 114 | } 115 | 116 | if (y < cy) { 117 | float j = y; 118 | y = cy; 119 | cy = j; 120 | } 121 | 122 | Tessellator tessellator = Tessellator.getInstance(); 123 | BufferBuilder bufferbuilder = tessellator.getBuffer(); 124 | GlStateManager.enableBlend(); 125 | GlStateManager.enableAlphaTest(); 126 | GlStateManager.polygonMode(GL_FRONT_AND_BACK, GL_FILL); 127 | GlStateManager.color4f(0.0f, 0.0f, 0.0f, 0.7f); 128 | GlStateManager.alphaFunc(GL11.GL_GREATER, 0.1f); 129 | GlStateManager.disableTexture(); 130 | bufferbuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); 131 | bufferbuilder.pos(x, cy, 0.0D).endVertex(); 132 | bufferbuilder.pos(cx, cy, 0.0D).endVertex(); 133 | bufferbuilder.pos(cx, y, 0.0D).endVertex(); 134 | bufferbuilder.pos(x, y, 0.0D).endVertex(); 135 | tessellator.draw(); 136 | GlStateManager.enableTexture(); 137 | GlStateManager.disableAlphaTest(); 138 | GlStateManager.disableBlend(); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/NaraeChat.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.Platform; 5 | import com.sun.jna.win32.StdCallLibrary; 6 | 7 | import kr.neko.sokcuri.naraechat.Keyboard.*; 8 | 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.gui.INestedGuiEventHandler; 11 | import net.minecraft.client.gui.screen.ControlsScreen; 12 | import net.minecraft.client.gui.screen.Screen; 13 | import net.minecraft.client.settings.KeyBinding; 14 | import net.minecraft.client.util.InputMappings; 15 | import net.minecraftforge.api.distmarker.Dist; 16 | import net.minecraftforge.api.distmarker.OnlyIn; 17 | import net.minecraftforge.client.event.GuiScreenEvent; 18 | import net.minecraftforge.client.settings.KeyModifier; 19 | import net.minecraftforge.common.MinecraftForge; 20 | import net.minecraftforge.event.TickEvent; 21 | import net.minecraftforge.eventbus.api.SubscribeEvent; 22 | 23 | import net.minecraftforge.fml.client.registry.ClientRegistry; 24 | import net.minecraftforge.fml.common.Mod; 25 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 26 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 27 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 28 | import org.apache.logging.log4j.LogManager; 29 | import org.apache.logging.log4j.Logger; 30 | 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | 34 | import static org.lwjgl.glfw.GLFW.*; 35 | 36 | // The value here should match an entry in the META-INF/mods.toml file 37 | @Mod("naraechat") 38 | @OnlyIn(Dist.CLIENT) 39 | public final class NaraeChat 40 | { 41 | private static KeyboardLayout keyboard = Hangul_Set_2_Layout.getInstance(); 42 | private static List keyboardArray = new ArrayList<>(); 43 | public static KeyBinding[] keyBindings; 44 | 45 | // Directly reference a log4j logger. 46 | private static final Logger LOGGER = LogManager.getLogger(); 47 | 48 | private int getBindingKeyCode(int n) { 49 | return keyBindings[n].getKey().getKeyCode(); 50 | } 51 | 52 | private void switchKeyboardLayout() { 53 | for (int i = 0; i < keyboardArray.size(); i++) { 54 | if (keyboard == keyboardArray.get(i)) { 55 | int n = (i + 1) % keyboardArray.size(); 56 | keyboard.cleanUp(); 57 | keyboard = keyboardArray.get(n); 58 | break; 59 | } 60 | } 61 | } 62 | 63 | public NaraeChat() { 64 | // Register the setup method for modloading 65 | FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); 66 | // Register the doClientStuff method for modloading 67 | FMLJavaModLoadingContext.get().getModEventBus().addListener(this::doClientStuff); 68 | 69 | // Register ourselves for server and other game events we are interested in 70 | MinecraftForge.EVENT_BUS.register(this); 71 | 72 | keyboardArray.add(QwertyLayout.getInstance()); 73 | keyboardArray.add(Hangul_Set_2_Layout.getInstance()); 74 | } 75 | 76 | public interface Imm32 extends StdCallLibrary { 77 | Imm32 INSTANCE = Native.loadLibrary("Imm32", Imm32.class); 78 | 79 | boolean ImmDisableIME(int ThreadID); 80 | } 81 | 82 | @SubscribeEvent 83 | public void proxyHangulSpecificKey(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 84 | 85 | int keyCode = event.getKeyCode(); 86 | int scanCode = event.getScanCode(); 87 | 88 | Minecraft mc = Minecraft.getInstance(); 89 | 90 | KeyModifier activeModifier = KeyModifier.getActiveModifier(); 91 | 92 | int glfwModifier = 0; 93 | if (activeModifier == KeyModifier.SHIFT) { 94 | glfwModifier = GLFW_MOD_SHIFT; 95 | } else if (activeModifier == KeyModifier.CONTROL) { 96 | glfwModifier = GLFW_MOD_CONTROL; 97 | } else if (activeModifier == KeyModifier.ALT) { 98 | glfwModifier = GLFW_MOD_ALT; 99 | } 100 | 101 | // 102 키보드 문제 수정. 한글/한자 키를 강재로 리매핑한다 102 | if (keyCode == -1 && scanCode == 0x1F2 || keyCode == -1 && scanCode == 0x1F1) { 103 | if (scanCode == 0x1F2) { 104 | keyCode = GLFW_KEY_RIGHT_ALT; 105 | scanCode = glfwGetKeyScancode(keyCode); 106 | } else if (scanCode == 0x1F1) { 107 | keyCode = GLFW_KEY_RIGHT_CONTROL; 108 | scanCode = glfwGetKeyScancode(keyCode); 109 | } 110 | 111 | event.setCanceled(true); 112 | } 113 | 114 | // 키 바인딩 설정창일 때 우측 CONTROL이나 ALT가 단독으로만 동작하게 만들기 115 | if (mc.currentScreen instanceof ControlsScreen) { 116 | ControlsScreen controlsScreen = (ControlsScreen)mc.currentScreen; 117 | if (keyCode == GLFW_KEY_RIGHT_CONTROL || keyCode == GLFW_KEY_RIGHT_ALT) { 118 | controlsScreen.keyPressed(keyCode, scanCode, glfwModifier); 119 | controlsScreen.buttonId = null; 120 | event.setCanceled(true); 121 | return; 122 | } 123 | } 124 | 125 | KeyModifier modifier = KeyModifier.getActiveModifier(); 126 | if (keyCode == GLFW_KEY_LEFT_CONTROL || keyCode == GLFW_KEY_RIGHT_CONTROL) { 127 | modifier = KeyModifier.NONE; 128 | } else if (keyCode == GLFW_KEY_LEFT_ALT || keyCode == GLFW_KEY_RIGHT_ALT) { 129 | modifier = KeyModifier.NONE; 130 | } 131 | 132 | if (keyBindings[0].matchesKey(keyCode, scanCode) && keyBindings[0].getKeyModifier() == modifier) { 133 | switchKeyboardLayout(); 134 | } 135 | 136 | if (keyBindings[1].matchesKey(keyCode, scanCode) && keyBindings[1].getKeyModifier() == modifier) { 137 | // hanja 138 | } 139 | } 140 | 141 | @SubscribeEvent 142 | public void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 143 | keyboard.onKeyPressed(event); 144 | } 145 | 146 | @SubscribeEvent 147 | public void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 148 | keyboard.onCharTyped(event); 149 | } 150 | 151 | @SubscribeEvent 152 | public void renderTick(TickEvent.RenderTickEvent event) { 153 | keyboard.renderTick(event); 154 | } 155 | private void setup(final FMLCommonSetupEvent event) 156 | { 157 | if (Platform.isWindows()) { 158 | Imm32.INSTANCE.ImmDisableIME(-1); 159 | } 160 | } 161 | 162 | private void doClientStuff(final FMLClientSetupEvent event) { 163 | // declare an array of key bindings 164 | keyBindings = new KeyBinding[2]; 165 | 166 | // instantiate the key bindings 167 | keyBindings[0] = new KeyBinding("key.naraechat.ime_switch.desc", GLFW_KEY_RIGHT_ALT, "key.naraechat.category"); 168 | keyBindings[1] = new KeyBinding("key.naraechat.hanja.desc", GLFW_KEY_RIGHT_CONTROL, "key.naraechat.category"); 169 | 170 | if (Platform.isWindows()) { 171 | keyBindings[0].setKeyModifierAndCode(KeyModifier.NONE, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_RIGHT_ALT)); 172 | keyBindings[1].setKeyModifierAndCode(KeyModifier.NONE, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_RIGHT_CONTROL)); 173 | } 174 | else if (Platform.isMac()) { 175 | keyBindings[0].setKeyModifierAndCode(KeyModifier.CONTROL, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_SPACE)); 176 | keyBindings[1].setKeyModifierAndCode(KeyModifier.SHIFT, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_ENTER)); 177 | } else { 178 | keyBindings[0].setKeyModifierAndCode(KeyModifier.CONTROL, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_SPACE)); 179 | keyBindings[1].setKeyModifierAndCode(KeyModifier.NONE, InputMappings.Type.KEYSYM.getOrMakeInput(GLFW_KEY_F9)); 180 | } 181 | 182 | // register all the key bindings 183 | for (int i = 0; i < keyBindings.length; ++i) 184 | { 185 | ClientRegistry.registerKeyBinding(keyBindings[i]); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /src/main/java/kr/neko/sokcuri/naraechat/Keyboard/Hangul_Set_2_Layout.java: -------------------------------------------------------------------------------- 1 | package kr.neko.sokcuri.naraechat.Keyboard; 2 | 3 | import kr.neko.sokcuri.naraechat.HangulProcessor; 4 | import kr.neko.sokcuri.naraechat.IMEIndicator; 5 | import kr.neko.sokcuri.naraechat.NaraeUtils; 6 | import kr.neko.sokcuri.naraechat.Obfuscated.*; 7 | import kr.neko.sokcuri.naraechat.Wrapper.TextComponentWrapper; 8 | import kr.neko.sokcuri.naraechat.Wrapper.TextFieldWidgetWrapper; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.gui.FontRenderer; 11 | import net.minecraft.client.renderer.BufferBuilder; 12 | import net.minecraft.client.renderer.Tessellator; 13 | import net.minecraft.client.renderer.vertex.DefaultVertexFormats; 14 | import net.minecraftforge.client.event.GuiScreenEvent; 15 | import net.minecraftforge.event.TickEvent; 16 | 17 | import org.lwjgl.opengl.GL11; 18 | 19 | import com.google.common.base.Splitter; 20 | import com.mojang.blaze3d.platform.GlStateManager; 21 | 22 | import java.awt.*; 23 | import java.util.List; 24 | 25 | import static org.lwjgl.glfw.GLFW.*; 26 | import static org.lwjgl.opengl.GL11.*; 27 | 28 | public class Hangul_Set_2_Layout implements KeyboardLayout { 29 | private final String layout = "`1234567890-=~!@#$%^&*()_+ㅂㅈㄷㄱㅅㅛㅕㅑㅐㅔ[]\\ㅃㅉㄸㄲㅆㅛㅕㅑㅒㅖ{}|ㅁㄴㅇㄹㅎㅗㅓㅏㅣ;'ㅁㄴㅇㄹㅎㅗㅓㅏㅣ:\"ㅋㅌㅊㅍㅠㅜㅡ,./ㅋㅌㅊㅍㅠㅜㅡ<>?"; 30 | private final String chosung_table = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"; 31 | private final String jungsung_table = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"; 32 | private final String jongsung_table = "\u0000ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"; 33 | private final List jungsung_ref_table = Splitter.on(",").splitToList(",,,,,,,,,ㅗㅏ,ㅗㅐ,ㅗㅣ,,,ㅜㅓ,ㅜㅔ,ㅜㅣ,,,ㅡㅣ,ㅣ"); 34 | private final List jongsung_ref_table = Splitter.on(",").splitToList(",,,ㄱㅅ,,ㄴㅈ,ㄴㅎ,,,ㄹㄱ,ㄹㅁ,ㄹㅂ,ㄹㅅ,ㄹㅌ,ㄹㅍ,ㄹㅎ,,,ㅂㅅ,,,,,,,,,"); 35 | private int assemblePosition = -1; 36 | 37 | private static KeyboardLayout instance = new Hangul_Set_2_Layout(); 38 | public static KeyboardLayout getInstance() { 39 | return instance; 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "한글 2벌식"; 45 | } 46 | 47 | @Override 48 | public String getIndicatorText() { 49 | return "한글"; 50 | } 51 | 52 | @Override 53 | public Color getIndicatorColor() { 54 | return new Color(0xFF, 0x7F, 0x00); 55 | } 56 | 57 | @Override 58 | public String getLayoutString() { 59 | return layout; 60 | } 61 | 62 | private int getQwertyIndexCodePoint(char ch) { 63 | return QwertyLayout.getInstance().getLayoutString().indexOf(ch); 64 | } 65 | 66 | boolean onBackspaceKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 67 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 68 | if (comp == null) return false; 69 | 70 | int cursorPosition = comp.getCursorPosition(); 71 | if (cursorPosition == 0 || cursorPosition != assemblePosition) return false; 72 | 73 | String text = comp.getText(); 74 | 75 | char ch = text.toCharArray()[cursorPosition - 1]; 76 | 77 | if (HangulProcessor.isHangulSyllables(ch)) { 78 | int code = ch - 0xAC00; 79 | int cho = code / (21 * 28); 80 | int jung = (code % (21 * 28)) / 28; 81 | int jong = (code % (21 * 28)) % 28; 82 | 83 | if (jong != 0) { 84 | char[] ch_arr = jongsung_ref_table.get(jong).toCharArray(); 85 | if (ch_arr.length == 2) { 86 | jong = jongsung_table.indexOf(ch_arr[0]); 87 | } else { 88 | jong = 0; 89 | } 90 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, jong); 91 | comp.modifyText(c); 92 | return true; 93 | } else { 94 | char[] ch_arr = jungsung_ref_table.get(jung).toCharArray(); 95 | if (ch_arr.length == 2) { 96 | jung = jungsung_table.indexOf(ch_arr[0]); 97 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, 0); 98 | comp.modifyText(c); 99 | return true; 100 | } else { 101 | char c = chosung_table.charAt(cho); 102 | comp.modifyText(c); 103 | return true; 104 | } 105 | } 106 | } else if (HangulProcessor.isHangulCharacter(ch)) { 107 | assemblePosition = -1; 108 | return false; 109 | } 110 | return false; 111 | } 112 | 113 | boolean onHangulCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 114 | boolean shift = (event.getModifiers() & 0x01) == 1; 115 | 116 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 117 | if (comp == null) return false; 118 | 119 | int codePoint = event.getCodePoint(); 120 | 121 | if (codePoint >= 65 && codePoint <= 90) { 122 | codePoint += 32; 123 | } 124 | 125 | if (codePoint >= 97 && codePoint <= 122) { 126 | if (shift) { 127 | codePoint -= 32; 128 | } 129 | } 130 | 131 | int idx = QwertyLayout.getInstance().getLayoutString().indexOf(codePoint); 132 | // System.out.println(String.format("idx: %d", idx)); 133 | if (idx == -1) { 134 | assemblePosition = -1; 135 | return false; 136 | } 137 | 138 | int cursorPosition = comp.getCursorPosition(); 139 | String text = comp.getText(); 140 | 141 | char prev = text.toCharArray()[cursorPosition - 1]; 142 | char curr = layout.toCharArray()[idx]; 143 | 144 | if (cursorPosition == 0) { 145 | if (!HangulProcessor.isHangulCharacter(curr)) return false; 146 | 147 | comp.writeText(String.valueOf(curr)); 148 | assemblePosition = comp.getCursorPosition(); 149 | } 150 | else if (cursorPosition == assemblePosition) { 151 | 152 | // 자음 + 모음 153 | if (HangulProcessor.isJaeum(prev) && HangulProcessor.isMoeum(curr)) { 154 | int cho = chosung_table.indexOf(prev); 155 | int jung = jungsung_table.indexOf(curr); 156 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, 0); 157 | comp.modifyText(c); 158 | assemblePosition = comp.getCursorPosition(); 159 | return true; 160 | } 161 | 162 | if (HangulProcessor.isHangulSyllables(prev)) { 163 | int code = prev - 0xAC00; 164 | int cho = code / (21 * 28); 165 | int jung = (code % (21 * 28)) / 28; 166 | int jong = (code % (21 * 28)) % 28; 167 | 168 | // 중성 합성 (ㅘ, ㅙ).. 169 | if (jong == 0 && HangulProcessor.isJungsung(prev, curr)) { 170 | jung = HangulProcessor.getJungsung(prev, curr); 171 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, 0); 172 | comp.modifyText(c); 173 | assemblePosition = comp.getCursorPosition(); 174 | return true; 175 | } 176 | 177 | // 종성 추가 178 | if (jong == 0 && HangulProcessor.isJongsung(curr)) { 179 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, HangulProcessor.getJongsung(curr)); 180 | comp.modifyText(c); 181 | assemblePosition = comp.getCursorPosition(); 182 | return true; 183 | } 184 | 185 | // 종성 받침 추가 186 | if (jong != 0 && HangulProcessor.isJongsung(prev, curr)) { 187 | jong = HangulProcessor.getJongsung(prev, curr); 188 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, jong); 189 | comp.modifyText(c); 190 | assemblePosition = comp.getCursorPosition(); 191 | return true; 192 | } 193 | 194 | // 종성에서 받침 하나 빼고 글자 만들기 195 | if (jong != 0 && HangulProcessor.isJungsung(curr)) { 196 | char[] tbl = jongsung_ref_table.get(jong).toCharArray(); 197 | int newCho = 0; 198 | if (tbl.length == 2) { 199 | newCho = chosung_table.indexOf(tbl[1]); 200 | jong = jongsung_table.indexOf(tbl[0]); 201 | } else { 202 | newCho = chosung_table.indexOf(jongsung_table.charAt(jong)); 203 | jong = 0; 204 | } 205 | 206 | char c = HangulProcessor.synthesizeHangulCharacter(cho, jung, jong); 207 | comp.modifyText(c); 208 | 209 | cho = newCho; 210 | jung = jungsung_table.indexOf(curr); 211 | code = HangulProcessor.synthesizeHangulCharacter(cho, jung, 0); 212 | comp.writeText(String.valueOf(Character.toChars(code))); 213 | assemblePosition = comp.getCursorPosition(); 214 | return true; 215 | } 216 | } 217 | } 218 | 219 | comp.writeText(String.valueOf(curr)); 220 | assemblePosition = comp.getCursorPosition(); 221 | return true; 222 | } 223 | 224 | public void typedTextField(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 225 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 226 | if (comp == null) return; 227 | 228 | char qwertyChar = event.getCodePoint(); 229 | int qwertyIndex = getQwertyIndexCodePoint(qwertyChar); 230 | if (qwertyIndex == -1) { 231 | assemblePosition = -1; 232 | return; 233 | } 234 | 235 | event.setCanceled(true); 236 | 237 | char curr = layout.toCharArray()[qwertyIndex]; 238 | int cursorPosition = comp.getCursorPosition(); 239 | 240 | if (cursorPosition == 0 || !HangulProcessor.isHangulCharacter(curr) || !onHangulCharTyped(event)) { 241 | comp.writeText(String.valueOf(curr)); 242 | assemblePosition = HangulProcessor.isHangulCharacter((curr)) ? comp.getCursorPosition() : -1; 243 | } 244 | } 245 | 246 | public void typedTextInput(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 247 | } 248 | 249 | @Override 250 | public void onCharTyped(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) { 251 | typedTextField(event); 252 | typedTextInput(event); 253 | } 254 | 255 | @Override 256 | public void onKeyPressed(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) { 257 | boolean isCanceled = false; 258 | 259 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 260 | if (comp == null) return; 261 | 262 | switch(event.getKeyCode()) { 263 | case GLFW_KEY_BACKSPACE: 264 | isCanceled = onBackspaceKeyPressed(event); 265 | break; 266 | } 267 | 268 | event.setCanceled(isCanceled); 269 | } 270 | 271 | 272 | void drawAssembleCharBox(int startX, int startY, int endX, int endY, int x, int width) { 273 | if (startX < endX) { 274 | int i = startX; 275 | startX = endX; 276 | endX = i; 277 | } 278 | 279 | if (startY < endY) { 280 | int j = startY; 281 | startY = endY; 282 | endY = j; 283 | } 284 | 285 | if (endX > x + width) { 286 | endX = x + width; 287 | } 288 | 289 | if (startX > x + width) { 290 | startX = x + width; 291 | } 292 | 293 | Tessellator tessellator = Tessellator.getInstance(); 294 | BufferBuilder bufferbuilder = tessellator.getBuffer(); 295 | GlStateManager.enableBlend(); 296 | GlStateManager.enableAlphaTest(); 297 | GlStateManager.polygonMode(GL_FRONT_AND_BACK, GL_FILL); 298 | GlStateManager.color4f(255.0f, 255.0f, 255.0f, 0.3f); 299 | GlStateManager.alphaFunc(GL11.GL_GREATER, 0.1f); 300 | GlStateManager.disableTexture(); 301 | bufferbuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); 302 | bufferbuilder.pos(startX, endY, 0.0D).endVertex(); 303 | bufferbuilder.pos(endX, endY, 0.0D).endVertex(); 304 | bufferbuilder.pos(endX, startY, 0.0D).endVertex(); 305 | bufferbuilder.pos(startX, startY, 0.0D).endVertex(); 306 | tessellator.draw(); 307 | GlStateManager.enableTexture(); 308 | GlStateManager.disableAlphaTest(); 309 | GlStateManager.disableBlend(); 310 | } 311 | 312 | @Override 313 | public void renderTick(TickEvent.RenderTickEvent event) { 314 | if(event.phase == TickEvent.Phase.END) { 315 | renderEndPhaseTick(event); 316 | } 317 | } 318 | 319 | @Override 320 | public void cleanUp() { 321 | assemblePosition = -1; 322 | } 323 | 324 | private void drawCharAssembleBox(TextComponentWrapper comp) { 325 | if (comp instanceof TextFieldWidgetWrapper) { 326 | TextFieldWidgetWrapper wrapper = (TextFieldWidgetWrapper) comp; 327 | FontRenderer fontRenderer = Minecraft.getInstance().fontRenderer; 328 | 329 | boolean enableBackgroundDrawing = wrapper.getEnableBackgroundDrawing(); 330 | boolean isEnabled = wrapper.isEnabled(); 331 | int adjustedWidth = wrapper.getAdjustedWidth(); 332 | int cursorPosition = wrapper.getCursorPosition(); 333 | int lineScrollOffset = wrapper.getLineScrollOffset(); 334 | int selectionEnd = wrapper.getSelectionEnd(); 335 | 336 | int width = wrapper.getWidth(); 337 | int height = wrapper.getHeight(); 338 | 339 | String trimStr = wrapper.getText().substring(lineScrollOffset); 340 | 341 | int x = enableBackgroundDrawing ? wrapper.getX() + 4 : wrapper.getX(); 342 | int y = enableBackgroundDrawing ? wrapper.getY() + (height - 8) / 2 : wrapper.getY(); 343 | int specifiedOffset = selectionEnd - lineScrollOffset; 344 | 345 | if (!isEnabled || !wrapper.isFocused() || assemblePosition != cursorPosition) { 346 | assemblePosition = -1; 347 | } 348 | 349 | if (assemblePosition == -1) return; 350 | if (trimStr.isEmpty()) return; 351 | if (cursorPosition == 0) return; 352 | if (trimStr.length() == 0) return; 353 | if (specifiedOffset == 0 || specifiedOffset - 1 >= trimStr.length()) return; 354 | int startX = x + fontRenderer.getStringWidth(trimStr.substring(0, specifiedOffset - 1)); 355 | int startY = y - 1; 356 | int endX = x + fontRenderer.getStringWidth(trimStr.substring(0, specifiedOffset)) - 1; 357 | int endY = y + 1 + 9; 358 | drawAssembleCharBox(startX, startY, endX, endY, x, width); 359 | } else { 360 | return; 361 | } 362 | 363 | } 364 | 365 | private void renderEndPhaseTick(TickEvent.RenderTickEvent event) { 366 | TextComponentWrapper comp = NaraeUtils.getTextComponent(); 367 | if (comp == null) return; 368 | 369 | drawCharAssembleBox(comp); 370 | IMEIndicator.Instance.drawIMEIndicator(this); 371 | } 372 | } 373 | --------------------------------------------------------------------------------