init
This commit is contained in:
commit
3f75c04731
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
**/run/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
4
.idea/encodings.xml
Normal file
4
.idea/encodings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8" />
|
||||||
|
</project>
|
||||||
20
.idea/gradle.xml
Normal file
20
.idea/gradle.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="distributionType" value="LOCAL" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleHome" value="$PROJECT_DIR$/../../../projEnv/gradle/gradle-8.12" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/forge-mod" />
|
||||||
|
<option value="$PROJECT_DIR$/velocity-plugin" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
21
.idea/misc.xml
Normal file
21
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ASMIdeaPluginConfiguration">
|
||||||
|
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||||
|
<groovy codeStyle="LEGACY" />
|
||||||
|
</component>
|
||||||
|
<component name="EntryPointsManager">
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="com.google.common.eventbus.Subscribe" />
|
||||||
|
</list>
|
||||||
|
<writeAnnotations>
|
||||||
|
<writeAnnotation name="com.google.inject.Inject" />
|
||||||
|
</writeAnnotations>
|
||||||
|
</component>
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
124
.idea/uiDesigner.xml
Normal file
124
.idea/uiDesigner.xml
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Palette2">
|
||||||
|
<group name="Swing">
|
||||||
|
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Button" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="RadioButton" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="CheckBox" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Label" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||||
|
<preferred-size width="-1" height="20" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
</group>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
39
build.gradle
Normal file
39
build.gradle
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url = "https://maven.neoforged.net/releases" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url = "https://maven.neoforged.net/releases" }
|
||||||
|
maven { url = "https://maven.minecraftforge.net/" }
|
||||||
|
maven { url = "https://maven.parchmentmc.org" }
|
||||||
|
maven { url = "https://maven.izzel.io/releases/" }
|
||||||
|
maven { url = "https://maven.bawnorton.com/releases" }
|
||||||
|
maven { url 'https://repo.lucko.me/' } // LuckPerms
|
||||||
|
}
|
||||||
|
processResources{
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
166
forge-mod/build.gradle
Normal file
166
forge-mod/build.gradle
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'org.jetbrains.dokka' version '1.9.10'
|
||||||
|
id 'io.franzbecker.gradle-lombok' version '3.0.0'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
|
id 'net.neoforged.moddev.legacyforge' version '2.0.103'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = mod_group_id
|
||||||
|
version = "${minecraft_version}-${mod_version}"
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
base {
|
||||||
|
archivesName = mod_id
|
||||||
|
}
|
||||||
|
configurations {
|
||||||
|
testImplementation {
|
||||||
|
canBeConsumed = false
|
||||||
|
}
|
||||||
|
testRuntimeClasspath {
|
||||||
|
canBeConsumed = false
|
||||||
|
attributes {
|
||||||
|
attribute(Attribute.of("net.neoforged.moddevgradle.legacy.minecraft_mappings.v2", String), "named")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//// 配置 Mixin
|
||||||
|
//mixin {
|
||||||
|
// add sourceSets.main, "${mod_id}.refmap.json"
|
||||||
|
// config "${mod_id}.mixins.json"
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 配置 LegacyForge 运行环境
|
||||||
|
legacyForge {
|
||||||
|
|
||||||
|
version = "${minecraft_version}-${forge_version}"
|
||||||
|
accessTransformers.from "src/main/resources/META-INF/accesstransformer.cfg"
|
||||||
|
parchment {
|
||||||
|
minecraftVersion = "${minecraft_version}"
|
||||||
|
mappingsVersion = "${mapping_lasting_version}"
|
||||||
|
}
|
||||||
|
runs {
|
||||||
|
configureEach {
|
||||||
|
systemProperty 'forge.logging.console.level', 'debug'
|
||||||
|
}
|
||||||
|
clientAuth {
|
||||||
|
devLogin = true
|
||||||
|
client()
|
||||||
|
}
|
||||||
|
client {
|
||||||
|
client()
|
||||||
|
}
|
||||||
|
data {
|
||||||
|
data()
|
||||||
|
}
|
||||||
|
server {
|
||||||
|
server()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mods {
|
||||||
|
"${mod_id}" {
|
||||||
|
sourceSet(sourceSets.main)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 源码配置
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDir 'src/main/java'
|
||||||
|
resources {
|
||||||
|
srcDirs += 'src/generated/resources'
|
||||||
|
include '**/**'
|
||||||
|
exclude '**/*.psd', '.cache'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 依赖声明
|
||||||
|
configurations {
|
||||||
|
library
|
||||||
|
implementation.extendsFrom library
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.24'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.24'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译任务优化
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
dokkaJavadoc {
|
||||||
|
outputDirectory = file("$buildDir/javadoc")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打包配置
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes([
|
||||||
|
'Specification-Title' : mod_id,
|
||||||
|
'Specification-Vendor' : mod_authors,
|
||||||
|
'Specification-Version' : '1',
|
||||||
|
'Implementation-Title' : project.name,
|
||||||
|
'Implementation-Version' : archiveVersion,
|
||||||
|
'Implementation-Vendor' : mod_authors,
|
||||||
|
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||||
|
// 'MixinConfigs' : "${mod_id}.mixin.json"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
finalizedBy 'reobfJar'
|
||||||
|
}
|
||||||
|
processResources {
|
||||||
|
def props = [
|
||||||
|
minecraft_version : minecraft_version,
|
||||||
|
minecraft_version_range: minecraft_version_range,
|
||||||
|
forge_version : forge_version,
|
||||||
|
forge_version_range : forge_version_range,
|
||||||
|
loader_version_range : loader_version_range,
|
||||||
|
mod_id : mod_id,
|
||||||
|
mod_name : mod_name,
|
||||||
|
mod_license : mod_license,
|
||||||
|
mod_version : mod_version,
|
||||||
|
mod_authors : mod_authors,
|
||||||
|
mod_description : mod_description
|
||||||
|
]
|
||||||
|
inputs.properties props
|
||||||
|
|
||||||
|
filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) {
|
||||||
|
expand props + [project: project]
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ShadowJar 禁止缓存优化
|
||||||
|
shadowJar {
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地 Maven 发布
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create('mavenJava', MavenPublication) {
|
||||||
|
artifact jar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url "file://${project.projectDir}/mcmodsrepo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources{
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
60
forge-mod/gradle.properties
Normal file
60
forge-mod/gradle.properties
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
|
||||||
|
# This is required to provide enough memory for the Minecraft decompilation process.
|
||||||
|
org.gradle.jvmargs=-Xmx3G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
neoForge.parchment.minecraftVersion=1.18.2
|
||||||
|
neoForge.parchment.mappingsVersion=2022.11.06
|
||||||
|
|
||||||
|
## Environment Properties
|
||||||
|
|
||||||
|
# The Minecraft version must agree with the Forge version to get a valid artifact
|
||||||
|
minecraft_version=1.18.2
|
||||||
|
# The Minecraft version range can use any release version of Minecraft as bounds.
|
||||||
|
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
|
||||||
|
# as they do not follow standard versioning conventions.
|
||||||
|
minecraft_version_range=[1.18.2,1.19)
|
||||||
|
# The Forge version must agree with the Minecraft version to get a valid artifact
|
||||||
|
forge_version=40.3.0
|
||||||
|
# The Forge version range can use any version of Forge as bounds or match the loader version range
|
||||||
|
forge_version_range=[40,)
|
||||||
|
# The loader version range can only use the major version of Forge/FML as bounds
|
||||||
|
loader_version_range=[40,)
|
||||||
|
# The mapping channel to use for mappings.
|
||||||
|
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
|
||||||
|
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
|
||||||
|
#
|
||||||
|
# | Channel | Version | |
|
||||||
|
# |-----------|----------------------|--------------------------------------------------------------------------------|
|
||||||
|
# | official | MCVersion | Official field/method names from Mojang mapping files |
|
||||||
|
# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official |
|
||||||
|
#
|
||||||
|
# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
|
||||||
|
# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
|
||||||
|
#
|
||||||
|
# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge.
|
||||||
|
# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started
|
||||||
|
mapping_channel=parchment
|
||||||
|
# The mapping version to query from the mapping channel.
|
||||||
|
# This must match the format required by the mapping channel.
|
||||||
|
mapping_version=2022.11.06-1.18.2
|
||||||
|
mapping_lasting_version=2022.11.06
|
||||||
|
# imgui_version=1.89.0
|
||||||
|
## Mod Properties
|
||||||
|
|
||||||
|
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
|
||||||
|
# Must match the String constant located in the main mod class annotated with @Mod.
|
||||||
|
mod_id=ltdcrossteleport
|
||||||
|
# The human-readable display name for the mod.
|
||||||
|
mod_name=Leisure Time Dock Mod
|
||||||
|
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||||
|
mod_license=MIT
|
||||||
|
# The mod version. See https://semver.org/
|
||||||
|
mod_version=0.0.0.1
|
||||||
|
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||||
|
# This should match the base package used for the mod sources.
|
||||||
|
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||||
|
mod_group_id=com.leisuretimedock.crossmod
|
||||||
|
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
|
||||||
|
mod_authors=R3944realms
|
||||||
|
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
|
||||||
|
mod_description=a Ltd Server's Mod
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.leisuretimedock.crossmod;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.IExtensionPoint;
|
||||||
|
import net.minecraftforge.fml.ModLoadingContext;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
|
||||||
|
@Mod(CrossTeleportMod.MOD_ID)
|
||||||
|
public class CrossTeleportMod {
|
||||||
|
public static final String MOD_ID ="ltdcrossteleport";
|
||||||
|
public static final ResourceLocation CHANNEL = new ResourceLocation(MOD_ID, "teleport");
|
||||||
|
|
||||||
|
public CrossTeleportMod() {
|
||||||
|
// 注册生命周期事件
|
||||||
|
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||||
|
() -> new IExtensionPoint.DisplayTest(() -> "ANY", (a, b) -> true));
|
||||||
|
|
||||||
|
}
|
||||||
|
@Mod.EventBusSubscriber(modid = MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||||
|
public static class ClientEvents {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
|
event.enqueueWork(NetworkHandler::register);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.leisuretimedock.crossmod;
|
||||||
|
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraftforge.network.NetworkRegistry;
|
||||||
|
import net.minecraftforge.network.simple.SimpleChannel;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static com.leisuretimedock.crossmod.client.PluginChannelClient.CHANNEL_ID;
|
||||||
|
|
||||||
|
public class NetworkHandler {
|
||||||
|
private static final String PROTOCOL_VERSION = "1";
|
||||||
|
private static SimpleChannel CHANNEL;
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
//TODO: 以后会做出双端版本,以让游戏服务器端可以允运行代理命令简化些流程
|
||||||
|
// 不需要注册普通 packet,因为我们只用 plugin message
|
||||||
|
CHANNEL = NetworkRegistry.newSimpleChannel(
|
||||||
|
new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport"),
|
||||||
|
() -> PROTOCOL_VERSION,
|
||||||
|
PROTOCOL_VERSION::equals,
|
||||||
|
PROTOCOL_VERSION::equals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendTeleportMessage(String serverName) {
|
||||||
|
// 构建 raw plugin message
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
buf.writeUtf(serverName);
|
||||||
|
|
||||||
|
Objects.requireNonNull(Minecraft.getInstance().getConnection()).send(
|
||||||
|
new ServerboundCustomPayloadPacket(
|
||||||
|
CrossTeleportMod.CHANNEL, buf
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public static void sendClientReady() {
|
||||||
|
if (Minecraft.getInstance().player == null) return;
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(baos);
|
||||||
|
try {
|
||||||
|
dos.writeUTF("client_ready");
|
||||||
|
dos.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
byte[] bytes = baos.toByteArray();
|
||||||
|
Objects.requireNonNull(Minecraft.getInstance().getConnection())
|
||||||
|
.send(new ServerboundCustomPayloadPacket(CHANNEL_ID, new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes))));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.Checkbox;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.renderer.GameRenderer;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.TextComponent;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class CrossServerGui extends Screen {
|
||||||
|
|
||||||
|
private static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport");
|
||||||
|
private static final ResourceLocation LOGO_TEXTURE = new ResourceLocation(CrossTeleportMod.MOD_ID, "textures/ltd_logo.png");
|
||||||
|
|
||||||
|
public CrossServerGui() {
|
||||||
|
super(new TextComponent("跨服菜单"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
int centerX = width / 2;
|
||||||
|
int centerY = height / 2;
|
||||||
|
int buttonWidth = 150;
|
||||||
|
int buttonHeight = 20;
|
||||||
|
int spacing = 5;
|
||||||
|
|
||||||
|
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY - buttonHeight - spacing,
|
||||||
|
buttonWidth, buttonHeight, new TextComponent("🏰 主城"), btn -> {
|
||||||
|
sendCustomPayload("connect:lobby");
|
||||||
|
onClose();
|
||||||
|
}));
|
||||||
|
|
||||||
|
addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY,
|
||||||
|
buttonWidth, buttonHeight, new TextComponent("🌲 生存服"), btn -> {
|
||||||
|
sendCustomPayload("connect:survival");
|
||||||
|
onClose();
|
||||||
|
}));
|
||||||
|
// 添加 Checkbox 控件
|
||||||
|
Checkbox overlayCheckbox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 5,
|
||||||
|
150, 20, new TextComponent("显示传送提示"), !OverlayRenderer.isShowOverlay()) {
|
||||||
|
@Override
|
||||||
|
public void onPress() {
|
||||||
|
super.onPress();
|
||||||
|
OverlayRenderer.setShow(this.selected());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addRenderableWidget(overlayCheckbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCustomPayload(String message) {
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.getConnection() != null) {
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
buf.writeUtf(message);
|
||||||
|
mc.getConnection().send(new ServerboundCustomPayloadPacket(CHANNEL_ID, buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTicks) {
|
||||||
|
// 背景
|
||||||
|
this.renderBackground(poseStack);
|
||||||
|
|
||||||
|
// Logo 渲染(缩放绘制)
|
||||||
|
renderLogo(poseStack);
|
||||||
|
|
||||||
|
// 渲染标题文字
|
||||||
|
drawCenteredString(poseStack, this.font, this.title.getString(), this.width / 2 + 5, 10, 0xFFFFFF);
|
||||||
|
|
||||||
|
// 渲染按钮等组件
|
||||||
|
super.render(poseStack, mouseX, mouseY, partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderLogo(PoseStack poseStack) {
|
||||||
|
RenderSystem.setShader(GameRenderer::getPositionTexShader);
|
||||||
|
RenderSystem.setShaderTexture(0, LOGO_TEXTURE);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
|
|
||||||
|
|
||||||
|
int logoWidth = 100; // 你可以改成 150、200 等
|
||||||
|
int logoHeight = 100; // 保持比例缩放
|
||||||
|
|
||||||
|
int x = (this.width - logoWidth) / 2;
|
||||||
|
int y = 15;
|
||||||
|
|
||||||
|
blit(poseStack, x, y, 0, 0, logoWidth, logoHeight, logoWidth, logoHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPauseScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.ClientRegistry;
|
||||||
|
import net.minecraftforge.event.TickEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||||
|
public class KeyBindingHandler {
|
||||||
|
public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key");
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRegisterKey(FMLClientSetupEvent event) {
|
||||||
|
event.enqueueWork(() -> ClientRegistry.registerKeyBinding(OPEN_GUI_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
||||||
|
public static class KeyHandler {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||||
|
if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) {
|
||||||
|
Minecraft.getInstance().setScreen(new CrossServerGui());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiComponent;
|
||||||
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.gui.OverlayRegistry;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
|
||||||
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||||
|
public class OverlayRenderer {
|
||||||
|
private static boolean showOverlay = false;
|
||||||
|
private static final Minecraft mc = Minecraft.getInstance();
|
||||||
|
public static boolean isShowOverlay() {
|
||||||
|
return !showOverlay || mc.player == null || mc.level == null;
|
||||||
|
}
|
||||||
|
public static void setShow(boolean show) {
|
||||||
|
showOverlay = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRender(FMLClientSetupEvent event) {
|
||||||
|
event.enqueueWork(() -> {
|
||||||
|
OverlayRegistry.registerOverlayTop(
|
||||||
|
"tran_server_tip",
|
||||||
|
(forgeIngameGui, poseStack, v, i, i1) -> {
|
||||||
|
if ( !showOverlay || mc.player == null || mc.level == null) return;
|
||||||
|
int x = 10;
|
||||||
|
int y = 10;
|
||||||
|
Font font = mc.font;
|
||||||
|
ItemRenderer itemRenderer = mc.getItemRenderer();
|
||||||
|
|
||||||
|
// 1. 原版钟物品
|
||||||
|
ItemStack clockStack = new ItemStack(Items.CLOCK);
|
||||||
|
|
||||||
|
// 2. 渲染钟图标(含动画帧)
|
||||||
|
itemRenderer.renderAndDecorateItem(clockStack, x, y);
|
||||||
|
itemRenderer.renderGuiItemDecorations(mc.font, clockStack, x, y);
|
||||||
|
|
||||||
|
// 3. 绘制提示文字
|
||||||
|
String keyText = KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage().getString(); // 可动态从 KeyMapping 获取
|
||||||
|
String text = "按 [" + keyText.toUpperCase() + "] 打开跨服传送菜单";
|
||||||
|
GuiComponent.drawString(poseStack,font, text, x + 20, y + 6, 0xFFFFFF);
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||||
|
import com.leisuretimedock.crossmod.NetworkHandler;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.commands.Commands;
|
||||||
|
import net.minecraft.network.Connection;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.TextComponent;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||||
|
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
@Slf4j
|
||||||
|
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
||||||
|
public class PluginChannelClient {
|
||||||
|
public static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "channel");
|
||||||
|
private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID+":channel";
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) {
|
||||||
|
log.info("[CrossTeleportMod] 玩家登录事件触发");
|
||||||
|
|
||||||
|
Connection connection = Objects.requireNonNull(Minecraft.getInstance().getConnection()).getConnection();
|
||||||
|
ChannelPipeline pipeline = connection.channel().pipeline();
|
||||||
|
|
||||||
|
log.info("[CrossTeleportMod] 当前管线内容: {}", pipeline.names());
|
||||||
|
|
||||||
|
if (pipeline.get(HANDLER_NAME) == null) {
|
||||||
|
pipeline.addBefore("packet_handler", HANDLER_NAME, new SimpleChannelInboundHandler<ClientboundCustomPayloadPacket>() {
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, ClientboundCustomPayloadPacket packet) {
|
||||||
|
log.info("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier());
|
||||||
|
|
||||||
|
if (!packet.getIdentifier().equals(CHANNEL_ID)) {
|
||||||
|
log.warn("[CrossTeleportMod] 未识别插件消息频道: {}", packet.getIdentifier());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendlyByteBuf buf = packet.getData();
|
||||||
|
try {
|
||||||
|
// 先读一个字符串但不使用它,出现空消息
|
||||||
|
buf.readUtf();
|
||||||
|
|
||||||
|
// 再读
|
||||||
|
String command = buf.readUtf();
|
||||||
|
|
||||||
|
log.info("[CrossTeleportMod] 收到指令: {}", command);
|
||||||
|
|
||||||
|
Minecraft.getInstance().execute(() -> {
|
||||||
|
PluginCommand.fromId(command).ifPresentOrElse(cmd -> {
|
||||||
|
switch (cmd) {
|
||||||
|
case OVERLAY_SHOW -> {
|
||||||
|
log.info("[CrossTeleportMod] 执行 OVERLAY_SHOW");
|
||||||
|
OverlayRenderer.setShow(true);
|
||||||
|
}
|
||||||
|
case OVERLAY_HIDE -> {
|
||||||
|
log.info("[CrossTeleportMod] 执行 OVERLAY_HIDE");
|
||||||
|
OverlayRenderer.setShow(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, () -> log.error("未知指令: {}", command));
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[CrossTeleportMod] 处理插件消息时发生错误: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("[CrossTeleportMod] 已添加插件消息处理器: {}", HANDLER_NAME);
|
||||||
|
NetworkHandler.sendClientReady();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.debug("[CrossTeleportMod] 管线中已存在插件消息处理器: {}", HANDLER_NAME);
|
||||||
|
NetworkHandler.sendClientReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onLogout(ClientPlayerNetworkEvent.LoggedOutEvent event) {
|
||||||
|
log.info("[CrossTeleportMod] 玩家注销事件触发");
|
||||||
|
|
||||||
|
Connection connection = event.getConnection();
|
||||||
|
if (connection != null) {
|
||||||
|
ChannelPipeline pipeline = connection.channel().pipeline();
|
||||||
|
|
||||||
|
log.info("[CrossTeleportMod] 当前管线内容: {}", pipeline.names());
|
||||||
|
|
||||||
|
if (pipeline.get(HANDLER_NAME) != null) {
|
||||||
|
pipeline.remove(HANDLER_NAME);
|
||||||
|
log.info("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME);
|
||||||
|
} else {
|
||||||
|
log.warn("[CrossTeleportMod] 未找到插件消息处理器: {}", HANDLER_NAME);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("[CrossTeleportMod] 玩家连接为空,无法移除插件处理器");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRegisterCommand(RegisterClientCommandsEvent event) {
|
||||||
|
event.getDispatcher().register(
|
||||||
|
Commands.literal("goto")
|
||||||
|
.then(Commands.argument("server", StringArgumentType.string())
|
||||||
|
.executes(ctx -> {
|
||||||
|
String server = StringArgumentType.getString(ctx, "server");
|
||||||
|
NetworkHandler.sendTeleportMessage(server);
|
||||||
|
ctx.getSource().sendSuccess(
|
||||||
|
new TextComponent("请求传送到 " + server), false);
|
||||||
|
return 1;
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.leisuretimedock.crossmod.client;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public enum PluginCommand {
|
||||||
|
OVERLAY_SHOW("overlay:show"),
|
||||||
|
OVERLAY_HIDE("overlay:hide");
|
||||||
|
|
||||||
|
public final String id;
|
||||||
|
|
||||||
|
PluginCommand(String id) { this.id = id; }
|
||||||
|
|
||||||
|
public static Optional<PluginCommand> fromId(String id) {
|
||||||
|
return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
forge-mod/src/main/resources/META-INF/mods.toml
Normal file
64
forge-mod/src/main/resources/META-INF/mods.toml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# This is an example mods.toml file. It contains the data relating to the loading mods.
|
||||||
|
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
|
||||||
|
# The overall format is standard TOML format, v0.5.0.
|
||||||
|
# Note that there are a couple of TOML lists in this file.
|
||||||
|
# Find more information on toml format here: https://github.com/toml-lang/toml
|
||||||
|
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
|
||||||
|
modLoader="javafml" #mandatory
|
||||||
|
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
|
||||||
|
loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
|
||||||
|
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
|
||||||
|
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
|
||||||
|
license="${mod_license}"
|
||||||
|
# A URL to refer people to when problems occur with this mod
|
||||||
|
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
|
||||||
|
# A list of mods - how many allowed here is determined by the individual mod loader
|
||||||
|
[[mods]] #mandatory
|
||||||
|
# The modid of the mod
|
||||||
|
modId="${mod_id}" #mandatory
|
||||||
|
# The version number of the mod
|
||||||
|
version="${mod_version}" #mandatory
|
||||||
|
# A display name for the mod
|
||||||
|
displayName="${mod_name}" #mandatory
|
||||||
|
# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/
|
||||||
|
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
|
||||||
|
# A URL for the "homepage" for this mod, displayed in the mod UI
|
||||||
|
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
||||||
|
# A file name (in the root of the mod JAR) containing a logo for display
|
||||||
|
logoFile="ltd_logo.png" #optional
|
||||||
|
# A text field displayed in the mod UI
|
||||||
|
#credits="" #optional
|
||||||
|
# A text field displayed in the mod UI
|
||||||
|
authors="${mod_authors}" #optional
|
||||||
|
# Display Test controls the display for your mod in the server connection screen
|
||||||
|
# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.
|
||||||
|
# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.
|
||||||
|
# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.
|
||||||
|
# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.
|
||||||
|
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.
|
||||||
|
#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional)
|
||||||
|
# The description text for the mod (multi line!) (#mandatory)
|
||||||
|
description='''${mod_description}'''
|
||||||
|
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
|
||||||
|
|
||||||
|
[[dependencies.${mod_id}]] #optional
|
||||||
|
# the modid of the dependency
|
||||||
|
modId="forge" #mandatory
|
||||||
|
# Does this dependency have to exist - if not, ordering below must be specified
|
||||||
|
mandatory=true #mandatory
|
||||||
|
# The version range of the dependency
|
||||||
|
versionRange="${forge_version_range}" #mandatory
|
||||||
|
# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory
|
||||||
|
# BEFORE - This mod is loaded BEFORE the dependency
|
||||||
|
# AFTER - This mod is loaded AFTER the dependency
|
||||||
|
ordering="NONE"
|
||||||
|
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
|
||||||
|
side="BOTH"
|
||||||
|
# Here's another dependency
|
||||||
|
[[dependencies.${mod_id}]]
|
||||||
|
modId="minecraft"
|
||||||
|
mandatory=true
|
||||||
|
# This version range declares a minimum of the current minecraft version up to but not including the next major version
|
||||||
|
versionRange="${minecraft_version_range}"
|
||||||
|
ordering="NONE"
|
||||||
|
side="BOTH"
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
||||||
|
"ltd.mod.client.key": "LTD跨服传送按键"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
BIN
forge-mod/src/main/resources/ltd_logo.png
Normal file
BIN
forge-mod/src/main/resources/ltd_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 672 KiB |
6
forge-mod/src/main/resources/pack.mcmeta
Normal file
6
forge-mod/src/main/resources/pack.mcmeta
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "${mod_id} resources",
|
||||||
|
"pack_format": 9
|
||||||
|
}
|
||||||
|
}
|
||||||
4
settings.gradle
Normal file
4
settings.gradle
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
plugins {
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
|
||||||
|
}
|
||||||
|
include(":velocity-plugin","forge-mod")
|
||||||
43
velocity-plugin/build.gradle
Normal file
43
velocity-plugin/build.gradle
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||||
|
id("xyz.jpenilla.run-velocity") version "2.3.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = plugin_group
|
||||||
|
version = plugin_version
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.velocitypowered.com/releases/' }
|
||||||
|
maven { url 'https://repo.lucko.me/' } // LuckPerms
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.24'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.24'
|
||||||
|
compileOnly 'com.velocitypowered:velocity-api:3.2.0-SNAPSHOT'
|
||||||
|
implementation("org.spongepowered:configurate-yaml:4.1.2")
|
||||||
|
annotationProcessor 'com.velocitypowered:velocity-api:3.2.0-SNAPSHOT'
|
||||||
|
compileOnly 'net.luckperms:api:5.4' // LuckPerms API
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
relocate 'com.google.common', 'shadowed.com.google.common'
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes 'Main-Class': 'com.yourname.CrossServerVelocityPlugin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processResources{
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
tasks {
|
||||||
|
runVelocity {
|
||||||
|
// Configure the Velocity version for our task.
|
||||||
|
// This is the only required configuration besides applying the plugin.
|
||||||
|
// Your plugin's jar (or shadowJar if present) will be used automatically.
|
||||||
|
velocityVersion("3.3.0-SNAPSHOT")
|
||||||
|
}
|
||||||
|
}
|
||||||
2
velocity-plugin/gradle.properties
Normal file
2
velocity-plugin/gradle.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
plugin_group=com.leisuretimedock.crossplugin
|
||||||
|
plugin_version=1.0.0.0
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.leisuretimedock.crossplugin;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.leisuretimedock.crossplugin.command.ReloadConfigCommand;
|
||||||
|
import com.leisuretimedock.crossplugin.handler.PluginChannelHandler;
|
||||||
|
import com.leisuretimedock.crossplugin.handler.PluginMessageHandler;
|
||||||
|
import com.leisuretimedock.crossplugin.manager.ConfigManager;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18n;
|
||||||
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
|
import com.velocitypowered.api.plugin.PluginManager;
|
||||||
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
id = Static.PLUGIN_ID,
|
||||||
|
name = Static.PLUGIN_NAME,
|
||||||
|
version = Static.PLUGIN_VERSION,
|
||||||
|
authors = "R3944Realms"
|
||||||
|
)
|
||||||
|
public class CrossPlugin {
|
||||||
|
|
||||||
|
private final ProxyServer server;
|
||||||
|
public final Logger logger;
|
||||||
|
public final PluginMessageHandler pluginMessageHandler;
|
||||||
|
public final PluginChannelHandler pluginChannelHandler;
|
||||||
|
public static boolean isLuckPermsEnabled;
|
||||||
|
public final PluginContainer pluginContainer;
|
||||||
|
@Inject
|
||||||
|
public CrossPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory ,PluginContainer pluginContainer) throws IOException {
|
||||||
|
this.server = server;
|
||||||
|
this.logger = logger;
|
||||||
|
ConfigManager config = new ConfigManager(dataDirectory);
|
||||||
|
I18n.addBundle(Locale.US);
|
||||||
|
I18n.addBundle(Locale.SIMPLIFIED_CHINESE);
|
||||||
|
I18n.init();
|
||||||
|
pluginChannelHandler = new PluginChannelHandler(server, logger, config);
|
||||||
|
pluginMessageHandler = new PluginMessageHandler(server, logger, config);
|
||||||
|
this.pluginContainer = pluginContainer;
|
||||||
|
server.getCommandManager().register(
|
||||||
|
server.getCommandManager()
|
||||||
|
.metaBuilder("ltdcs")
|
||||||
|
.aliases("ltd", "l")
|
||||||
|
.plugin(pluginContainer)
|
||||||
|
.build()
|
||||||
|
,
|
||||||
|
new ReloadConfigCommand(config)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyInit(ProxyInitializeEvent event) {
|
||||||
|
server.getChannelRegistrar().register(PluginMessageHandler.CHANNEL_ID, PluginChannelHandler.CHANNEL_ID);
|
||||||
|
server.getEventManager().register(this, pluginChannelHandler);
|
||||||
|
server.getEventManager().register(this, pluginMessageHandler);
|
||||||
|
isLuckPermsEnabled = server.getPluginManager().getPlugin("luckperms").isPresent();
|
||||||
|
logger.info("[INIT] Plugin initialized, channel registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.leisuretimedock.crossplugin;
|
||||||
|
|
||||||
|
|
||||||
|
public class Static {
|
||||||
|
public static final String MOD_ID = "ltdcrossteleport";
|
||||||
|
public static final String PLUGIN_ID = "ltdcrossserver";
|
||||||
|
public static final String PLUGIN_NAME = "LTD CrossServer Velocity Plugin";
|
||||||
|
public static final String PLUGIN_VERSION = "1.0.0.2";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.leisuretimedock.crossplugin.command;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossplugin.manager.ConfigManager;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18n;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18nKeyEnum;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ReloadConfigCommand implements SimpleCommand {
|
||||||
|
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
public static final String PERMISSION_RELOAD = "ltdcrossserver.reload";
|
||||||
|
public static final String PERMISSION_HELP = "ltdcrossserver.help";
|
||||||
|
public ReloadConfigCommand(ConfigManager configManager) {
|
||||||
|
this.configManager = configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(SimpleCommand.Invocation invocation) {
|
||||||
|
CommandSource source = invocation.source();
|
||||||
|
String[] args = invocation.arguments();
|
||||||
|
// /ltdcrossserver
|
||||||
|
if (args.length == 0) {
|
||||||
|
source.sendMessage(I18n.translatable(PERMISSION_HELP, NamedTextColor.YELLOW));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
switch (subCommand) {
|
||||||
|
case "reload" -> handleReload(source);
|
||||||
|
default -> source.sendMessage(I18n.translatable(I18nKeyEnum.UNKNOWN_COMMAND, NamedTextColor.YELLOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
private void handleReload(CommandSource source) {
|
||||||
|
// 控制台允许,玩家检查权限
|
||||||
|
if (source instanceof Player player && !player.hasPermission(PERMISSION_RELOAD)) {
|
||||||
|
source.sendMessage(I18n.translatable(I18nKeyEnum.NO_PERMISSION_TO_USE_THIS_COMMAND, NamedTextColor.RED, Component.text(PERMISSION_RELOAD)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
configManager.reload();
|
||||||
|
source.sendMessage(I18n.translatable(I18nKeyEnum.RELOAD_CONFIG_SUCCESSFUL, NamedTextColor.GREEN));
|
||||||
|
} catch (Exception e) {
|
||||||
|
source.sendMessage(I18n.translatable(I18nKeyEnum.FAILED_TO_RELOAD_CONFIG, NamedTextColor.RED));
|
||||||
|
log.error("Failed to reload config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.leisuretimedock.crossplugin.handler;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossplugin.Static;
|
||||||
|
import com.leisuretimedock.crossplugin.manager.ConfigManager;
|
||||||
|
import com.leisuretimedock.crossplugin.manager.OverlayManager;
|
||||||
|
import com.leisuretimedock.crossplugin.manager.ServerManager;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18n;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18nKeyEnum;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class PluginChannelHandler {
|
||||||
|
|
||||||
|
public static final MinecraftChannelIdentifier CHANNEL_ID =
|
||||||
|
MinecraftChannelIdentifier.create(Static.MOD_ID, "channel");
|
||||||
|
|
||||||
|
private final ProxyServer proxy;
|
||||||
|
private final Logger logger;
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
private final ServerManager serverManager;
|
||||||
|
private final Set<Player> waitingForReady = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
public PluginChannelHandler(ProxyServer proxy, Logger logger, ConfigManager configManager) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
this.logger = logger;
|
||||||
|
this.configManager = configManager;
|
||||||
|
this.serverManager = new ServerManager(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
if (!event.getIdentifier().equals(CHANNEL_ID)) return;
|
||||||
|
if (!(event.getSource() instanceof Player player)) return;
|
||||||
|
|
||||||
|
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) {
|
||||||
|
String command = in.readUTF();
|
||||||
|
logger.debug("Received plugin message from {}: {}", player.getUsername(), command);
|
||||||
|
|
||||||
|
if (command.startsWith("teleport:")) {
|
||||||
|
String targetServer = command.substring("teleport:".length());
|
||||||
|
proxy.getServer(targetServer).ifPresentOrElse(server -> {
|
||||||
|
player.createConnectionRequest(server).fireAndForget();
|
||||||
|
logger.debug("Teleporting {} to {}", player.getUsername(), targetServer);
|
||||||
|
}, () -> {
|
||||||
|
player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(targetServer)));
|
||||||
|
});
|
||||||
|
} else if ("client_ready".equals(command)) {
|
||||||
|
// 收到客户端准备消息
|
||||||
|
if (waitingForReady.remove(player)) {
|
||||||
|
logger.debug("[CrossTeleportMod] {} is ready, sending overlay and server list", player.getUsername());
|
||||||
|
OverlayManager.showOverlay(player);
|
||||||
|
//TODO:未来计划使对应客户端mod可加载来自插件的自定义服务器列表
|
||||||
|
// OverlayManager.sendServerList(player, serverManager.getAvailableServers());
|
||||||
|
} else {
|
||||||
|
logger.debug("[CrossTeleportMod] Received client_ready from {}, but was not waiting", player.getUsername());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("[CrossTeleportMod] Unknown plugin command from {}: {}", player.getUsername(), command);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("[CrossTeleportMod] Error parsing plugin message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPlayerJoin(ServerConnectedEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
String currentServer = event.getServer().getServerInfo().getName();
|
||||||
|
|
||||||
|
logger.debug("[CrossTeleportMod] Player {} joined server {}", player.getUsername(), currentServer);
|
||||||
|
|
||||||
|
if (configManager.getOverlayServers().contains(currentServer)) {
|
||||||
|
// 标记此玩家等待客户端准备确认
|
||||||
|
waitingForReady.add(player);
|
||||||
|
logger.debug("[CrossTeleportMod] Added {} to waitingForReady set", player.getUsername());
|
||||||
|
} else {
|
||||||
|
// 不是 lobby,隐藏 overlay
|
||||||
|
OverlayManager.hideOverlay(player);
|
||||||
|
logger.debug("[CrossTeleportMod] Hide overlay for player {}", player.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.leisuretimedock.crossplugin.handler;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossplugin.CrossPlugin;
|
||||||
|
import com.leisuretimedock.crossplugin.Static;
|
||||||
|
import com.leisuretimedock.crossplugin.manager.ConfigManager;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18n;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18nKeyEnum;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class PluginMessageHandler {
|
||||||
|
public static final MinecraftChannelIdentifier CHANNEL_ID =
|
||||||
|
MinecraftChannelIdentifier.create(Static.MOD_ID, "teleport");
|
||||||
|
private static final String PERMISSION_HEAD = Static.MOD_ID + ".goto.";
|
||||||
|
|
||||||
|
private final ProxyServer server;
|
||||||
|
private final Logger logger;
|
||||||
|
private final ConfigManager config;
|
||||||
|
|
||||||
|
public PluginMessageHandler(ProxyServer server, Logger logger, ConfigManager config) {
|
||||||
|
this.server = server;
|
||||||
|
this.logger = logger;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
if (!(event.getSource() instanceof Player player)) return;
|
||||||
|
if (!event.getIdentifier().equals(CHANNEL_ID)) return;
|
||||||
|
|
||||||
|
byte[] data = event.getData();
|
||||||
|
String raw = new String(data, 1, data.length - 1, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
logger.info("Received plugin message from {}: {}", player.getUsername(), raw);
|
||||||
|
|
||||||
|
// 处理 connect:key 模式
|
||||||
|
if (raw.startsWith("connect:")) {
|
||||||
|
String key = raw.substring("connect:".length());
|
||||||
|
String targetServerName = config.resolveServerName(key);
|
||||||
|
|
||||||
|
if (isAlreadyOnServer(player, targetServerName)) {
|
||||||
|
player.sendMessage(I18n.translatable(I18nKeyEnum.ALREADY_ON_SERVER, NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.getServer(targetServerName).ifPresentOrElse(
|
||||||
|
srv -> player.createConnectionRequest(srv).fireAndForget(),
|
||||||
|
() -> player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(targetServerName)))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通 serverName 模式
|
||||||
|
String permissionNode = PERMISSION_HEAD + raw;
|
||||||
|
//这个权限是 "ltdcrossteleport.goto.<xx服务器名>"
|
||||||
|
if (CrossPlugin.isLuckPermsEnabled && !player.hasPermission(permissionNode)) {
|
||||||
|
player.sendMessage(I18n.translatable(I18nKeyEnum.NO_PERMISSION_TO_TRANS_THIS_SERVER, NamedTextColor.RED, Component.text(raw)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAlreadyOnServer(player, raw)) {
|
||||||
|
player.sendMessage(I18n.translatable(I18nKeyEnum.ALREADY_ON_SERVER, NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.getServer(raw).ifPresentOrElse(
|
||||||
|
srv -> player.createConnectionRequest(srv).fireAndForget(),
|
||||||
|
() -> player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(raw)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAlreadyOnServer(Player player, String serverName) {
|
||||||
|
return player.getCurrentServer()
|
||||||
|
.map(current -> current.getServerInfo().getName().equalsIgnoreCase(serverName))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.leisuretimedock.crossplugin.manager;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ConfigManager {
|
||||||
|
private final Path configPath;
|
||||||
|
private final YamlConfigurationLoader loader;
|
||||||
|
private final Map<String, String> serverAliases = new ConcurrentHashMap<>();
|
||||||
|
private final Set<String> overlayServers = ConcurrentHashMap.newKeySet();
|
||||||
|
private ConfigurationNode rootNode;
|
||||||
|
|
||||||
|
public ConfigManager(Path configDir) throws IOException {
|
||||||
|
this.configPath = configDir.resolve("config.yml");
|
||||||
|
copyDefaultConfigIfAbsent(this.configPath);
|
||||||
|
this.loader = YamlConfigurationLoader.builder()
|
||||||
|
.path(configPath)
|
||||||
|
.indent(2)
|
||||||
|
.build();
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads or creates the configuration file
|
||||||
|
* @throws IOException if configuration cannot be loaded
|
||||||
|
*/
|
||||||
|
public synchronized void load() throws IOException {
|
||||||
|
try {
|
||||||
|
rootNode = loader.load();
|
||||||
|
|
||||||
|
// Load server aliases
|
||||||
|
ConfigurationNode aliasesNode = rootNode.node("server-aliases");
|
||||||
|
if (aliasesNode.virtual() || aliasesNode.empty()) {
|
||||||
|
aliasesNode.node("survival").set("survival");
|
||||||
|
aliasesNode.node("lobby").set("lobby");
|
||||||
|
loader.save(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
serverAliases.clear();
|
||||||
|
aliasesNode.childrenMap().forEach((key, node) -> {
|
||||||
|
String realName = node.getString();
|
||||||
|
if (realName != null) serverAliases.put(key.toString(), realName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load overlay servers
|
||||||
|
ConfigurationNode overlayNode = rootNode.node("show-overlay-servers");
|
||||||
|
if (overlayNode.virtual() || overlayNode.empty()) {
|
||||||
|
overlayNode.setList(String.class, List.of("lobby"));
|
||||||
|
loader.save(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayServers.clear();
|
||||||
|
for (ConfigurationNode node : overlayNode.childrenList()) {
|
||||||
|
String name = node.getString();
|
||||||
|
if (name != null) overlayServers.add(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Loaded {} server aliases from config", serverAliases.size());
|
||||||
|
log.info("Loaded {} overlay servers from config", overlayServers.size());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to load configuration from {}", configPath, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the configuration from disk
|
||||||
|
*/
|
||||||
|
public synchronized void reload() throws IOException {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current configuration to disk
|
||||||
|
*/
|
||||||
|
public synchronized void save() throws IOException {
|
||||||
|
loader.save(rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a server name from its alias
|
||||||
|
* @param key The alias or real server name to resolve
|
||||||
|
* @return The real server name, or the input if no alias exists
|
||||||
|
*/
|
||||||
|
public String resolveServerName(String key) {
|
||||||
|
return serverAliases.getOrDefault(key, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all server aliases as an unmodifiable map
|
||||||
|
*/
|
||||||
|
public Map<String, String> getServerAliases() {
|
||||||
|
return Collections.unmodifiableMap(serverAliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or updates a server alias
|
||||||
|
* @param alias The alias to add/update
|
||||||
|
* @param realName The real server name
|
||||||
|
*/
|
||||||
|
public synchronized void setServerAlias(String alias, String realName) throws IOException {
|
||||||
|
rootNode.node("server-aliases", alias).set(realName);
|
||||||
|
serverAliases.put(alias, realName);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a server alias
|
||||||
|
* @param alias The alias to remove
|
||||||
|
*/
|
||||||
|
public synchronized void removeServerAlias(String alias) throws IOException {
|
||||||
|
rootNode.node("server-aliases").removeChild(alias);
|
||||||
|
serverAliases.remove(alias);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servers should show overlay
|
||||||
|
* @return lists Show OverLay when join they
|
||||||
|
*/
|
||||||
|
public Set<String> getOverlayServers() {
|
||||||
|
return Collections.unmodifiableSet(overlayServers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyDefaultConfigIfAbsent(Path configPath) throws IOException {
|
||||||
|
if (Files.notExists(configPath)) {
|
||||||
|
// 先创建父目录(如果不存在)
|
||||||
|
Files.createDirectories(configPath.getParent());
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
|
||||||
|
if (in == null) {
|
||||||
|
throw new IOException("Missing embedded config.yml in resources!");
|
||||||
|
}
|
||||||
|
Files.copy(in, configPath);
|
||||||
|
log.info("Default config.yml copied to {}", configPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.leisuretimedock.crossplugin.manager;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossplugin.handler.PluginChannelHandler;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18n;
|
||||||
|
import com.leisuretimedock.crossplugin.messages.I18nKeyEnum;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OverlayManager {
|
||||||
|
|
||||||
|
|
||||||
|
public static void showOverlay(Player player) {
|
||||||
|
sendRawCommand(player, "overlay:show");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideOverlay(Player player) {
|
||||||
|
sendRawCommand(player, "overlay:hide");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendRawCommand(Player player, String command) {
|
||||||
|
if (!player.isActive()) return;
|
||||||
|
try (var out = new java.io.ByteArrayOutputStream();
|
||||||
|
var data = new java.io.DataOutputStream(out)) {
|
||||||
|
|
||||||
|
data.writeUTF(command); // 这里写入字符串(包括长度前缀)
|
||||||
|
data.flush();
|
||||||
|
|
||||||
|
player.sendPluginMessage(
|
||||||
|
PluginChannelHandler.CHANNEL_ID,
|
||||||
|
out.toByteArray()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 处理异常,日志等等
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//TODO : 客户端模组未来待实现
|
||||||
|
public static void sendServerList(Player player, Iterable<ServerManager.ServerInfo> servers) {
|
||||||
|
try (var out = new java.io.ByteArrayOutputStream();
|
||||||
|
var data = new java.io.DataOutputStream(out)) {
|
||||||
|
|
||||||
|
data.writeUTF("gui:server_list");
|
||||||
|
|
||||||
|
List<ServerManager.ServerInfo> list = new ArrayList<>();
|
||||||
|
servers.forEach(list::add);
|
||||||
|
|
||||||
|
data.writeInt(list.size());
|
||||||
|
for (var server : list) {
|
||||||
|
data.writeUTF(server.id()); // 名称
|
||||||
|
data.writeUTF(server.motd()); // MOTD
|
||||||
|
data.writeUTF(server.id()); // 目标 ID
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendPluginMessage(
|
||||||
|
PluginChannelHandler.CHANNEL_ID,
|
||||||
|
out.toByteArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
player.sendMessage(I18n.translatable(I18nKeyEnum.FAILED_TO_SEND_SERVER_LIST, NamedTextColor.RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.leisuretimedock.crossplugin.manager;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
//TODO: 未来计划
|
||||||
|
public class ServerManager {
|
||||||
|
private final ProxyServer proxy;
|
||||||
|
private final Map<String, ServerInfo> serverMap = new HashMap<>();
|
||||||
|
|
||||||
|
public ServerManager(ProxyServer proxy) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
|
||||||
|
// 示例:静态初始化可跳转服务器
|
||||||
|
registerServer("lobby", "大厅服务器");
|
||||||
|
registerServer("survival", "生存服务器");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerServer(String id, String motd) {
|
||||||
|
Optional<RegisteredServer> server = proxy.getServer(id);
|
||||||
|
server.ifPresent(s -> serverMap.put(id, new ServerInfo(id, motd)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ServerInfo> getAvailableServers() {
|
||||||
|
return serverMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<RegisteredServer> getServerById(String id) {
|
||||||
|
return proxy.getServer(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ServerInfo(String id, String motd) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.leisuretimedock.crossplugin.messages;
|
||||||
|
|
||||||
|
import com.leisuretimedock.crossplugin.Static;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
|
import net.kyori.adventure.translation.GlobalTranslator;
|
||||||
|
import net.kyori.adventure.translation.TranslationRegistry;
|
||||||
|
import net.kyori.adventure.util.UTF8ResourceBundleControl;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class I18n {
|
||||||
|
public static TranslationRegistry registry = TranslationRegistry.create(Key.key(Static.MOD_ID +":value"));
|
||||||
|
public static HashMap<Locale,ResourceBundle> bundles = new HashMap<>();
|
||||||
|
public static void init() {
|
||||||
|
for (Map.Entry<Locale, ResourceBundle> bundle : bundles.entrySet()) {
|
||||||
|
registry.registerAll(bundle.getKey(), bundle.getValue(), true);
|
||||||
|
}
|
||||||
|
GlobalTranslator.translator().addSource(registry);
|
||||||
|
}
|
||||||
|
public static ResourceBundle getBundle(Locale locale) {
|
||||||
|
return ResourceBundle.getBundle("crossserver.Bundle."+ locale.toLanguageTag().replace('-','_'), locale, UTF8ResourceBundleControl.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addBundle(Locale locale) {
|
||||||
|
if (bundles.containsKey(locale)) {
|
||||||
|
log.warn("Duplicate bundle locale: {}", locale);
|
||||||
|
}
|
||||||
|
else bundles.put(locale, getBundle(locale));
|
||||||
|
}
|
||||||
|
// 基础无颜色版本
|
||||||
|
public static Component translatable(String key, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(String key) {
|
||||||
|
return Component.translatable(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(String key, String fallback, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, fallback, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(String key, String fallback) {
|
||||||
|
return Component.translatable(key, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key) {
|
||||||
|
return Component.translatable(key.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, String fallback, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), fallback, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, String fallback) {
|
||||||
|
return Component.translatable(key.getKey(), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下面是带颜色或样式版本,核心是先创建基础 Component,再调用 color 或 style
|
||||||
|
|
||||||
|
public static Component translatable(String key, NamedTextColor color, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, args).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(String key, Style style, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, args).style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, NamedTextColor color, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), args).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, Style style, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), args).style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带 fallback 的带颜色或样式版本
|
||||||
|
|
||||||
|
public static Component translatable(String key, String fallback, NamedTextColor color, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, fallback, args).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(String key, String fallback, Style style, ComponentLike... args) {
|
||||||
|
return Component.translatable(key, fallback, args).style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, String fallback, NamedTextColor color, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), fallback, args).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component translatable(I18nKeyEnum key, String fallback, Style style, ComponentLike... args) {
|
||||||
|
return Component.translatable(key.getKey(), fallback, args).style(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.leisuretimedock.crossplugin.messages;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum I18nKeyEnum {
|
||||||
|
NO_PERMISSION_TO_TRANS_THIS_SERVER("ltd.plugin.trans.failed.no_permission"),
|
||||||
|
NO_PERMISSION_TO_USE_THIS_COMMAND("ltd.plugin.command.no_permission"),
|
||||||
|
SERVER_NOT_FOUND("ltd.plugin.trans.failed.server_not_found"),
|
||||||
|
ALREADY_ON_SERVER("ltd.plugin.trans.failed.already_on_server"),
|
||||||
|
FAILED_TO_SEND_SERVER_LIST("ltd.plugin.send_server_list.failed"),
|
||||||
|
FAILED_TO_RELOAD_CONFIG("ltd.plugin.reload.failed.error"),
|
||||||
|
RELOAD_CONFIG_SUCCESSFUL("ltd.plugin.reload.successful"),
|
||||||
|
COMMAND_HELP("ltd.plugin.help.command"),
|
||||||
|
UNKNOWN_COMMAND("ltd.plugin.command.unknown_command"),
|
||||||
|
;
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
I18nKeyEnum(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
velocity-plugin/src/main/resources/config.yml
Normal file
21
velocity-plugin/src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# LTD CrossServer Plugin Configuration
|
||||||
|
# 闲趣时坞跨服传送插件配置文件
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# Server Aliases / 服务器别名映射
|
||||||
|
# 玩家使用 /goto 命令时可以输入别名
|
||||||
|
# Players can use these aliases with /goto
|
||||||
|
# 格式: alias: real-server-name
|
||||||
|
# Format: 别名: 实际服务器名称
|
||||||
|
server-aliases:
|
||||||
|
survival: survival # 生存服 / Survival server
|
||||||
|
lobby: lobby # 大厅服 / Lobby server
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# Show Overlay Servers / 显示 Overlay 的服务器列表
|
||||||
|
# 玩家加入这些服务器时客户端将显示 Overlay 提示
|
||||||
|
# Clients will show overlay tips when joining these servers
|
||||||
|
# 你可以添加多个服务器名
|
||||||
|
# You can add multiple server names
|
||||||
|
show-overlay-servers:
|
||||||
|
- lobby
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
ltd.plugin.trans.failed.no_permission=You don't have permission to teleport to this server ({0})!
|
||||||
|
ltd.plugin.trans.failed.server_not_found=The target server ({0}) was not found!
|
||||||
|
ltd.plugin.trans.failed.already_on_server=You have already been in that server.
|
||||||
|
ltd.plugin.send_server_list.failed=Failed to send server list.
|
||||||
|
ltd.plugin.reload.successful=Config is reloaded.
|
||||||
|
ltd.plugin.reload.failed.error=Failed to reload config, please check the server logs.
|
||||||
|
ltd.plugin.command.no_permission=You don't have permission to execute this command.
|
||||||
|
ltd.plugin.help.command=Usage: /ltdsc reload.
|
||||||
|
ltd.plugin.command.unknown_command=Unknown sub command: {0}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
ltd.plugin.trans.no_permission=你没有权限传送到该服务器!({0})
|
||||||
|
ltd.plugin.trans.server_not_found=目标服务器不存在!({0})
|
||||||
|
ltd.plugin.trans.already_on_server=你已经在该服务器上了。
|
||||||
|
ltd.plugin.send_server_list.failed=发送服务器列表失败。
|
||||||
|
ltd.plugin.command.no_permission=你没有权限重载去执行该指令,需要权限节点:{0}!
|
||||||
|
ltd.plugin.reload.successful=配置已重新加载。
|
||||||
|
ltd.plugin.reload.failed.error=重新加载配置时出错,请查看控制台日志。
|
||||||
|
ltd.plugin.help.command=用法: /ltdsc reload.
|
||||||
|
ltd.plugin.command.unknown_command=未知的子命令: {0}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user