diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..e037b80 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,61 @@ +# A deployment template that works out of the box +# It supports these objectives: +# - Deploy to Maven (Build Job) [Secrets: MAVEN_USER, MAVEN_PASS] +# - Deploy to CurseForge (Upload Job) [Secrets: CURSEFORGE_TOKEN] +# - Deploy to Modrinth (Upload Job) [Secrets: MODRINTH_TOKEN] + +name: Deploy + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Grant Execute Permission for gradlew + run: chmod +x gradlew + + - name: Read gradle.properties + uses: BrycensRanch/read-properties-action@v1 + id: properties + with: + file: gradle.properties + all: true + + - name: Setup Java + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + cache: gradle + + - name: Publish to Maven + if: steps.properties.outputs.publish_to_maven == 'true' && steps.properties.outputs.publish_to_local_maven == 'true' + uses: gradle/gradle-build-action@v2 + with: + arguments: | + publish + -P${{ steps.properties.outputs.maven_name }}Username=${{ secrets.MAVEN_USER }} + -P${{ steps.properties.outputs.maven_name }}Password=${{ secrets.MAVEN_PASS }} + + - name: Publish to CurseForge + if: steps.properties.outputs.publish_to_curseforge == 'true' + uses: gradle/gradle-build-action@v2 + env: + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + with: + arguments: curseforge + + - name: Publish to Modrinth + if: steps.properties.outputs.publish_to_modrinth == 'true' + uses: gradle/gradle-build-action@v2 + env: + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + with: + arguments: modrinth diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a5c40b2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## [1.0.0] - 2023-09-15 + +### Added +- This is a default template changelog that follows the [KeepAChangelog Convention](https://keepachangelog.com/en/1.1.0/) \ No newline at end of file diff --git a/LICENSE b/LICENSE index fefacc2..99a3357 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,11 @@ -MIT License +All Rights Reserved -Copyright (c) 2022 CleanroomMC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Copyright (c) 2024 Octol1ttle THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index ea287ad..08e33f2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Template workspace for modding Minecraft 1.12.2. Licensed under MIT, it is made for public use. -This template currently utilizies **Gradle 8.1.1** + **[RetroFuturaGradle](https://github.com/GTNewHorizons/RetroFuturaGradle) 1.3.6** + **Forge 14.23.5.2847**. +This template currently utilizies **Gradle 8.7** + **[RetroFuturaGradle](https://github.com/GTNewHorizons/RetroFuturaGradle) 1.3.35** + **Forge 14.23.5.2847**. With **coremod and mixin support** that is easy to configure. @@ -14,3 +14,7 @@ With **coremod and mixin support** that is easy to configure. 4. Open the project folder in IDEA. 5. Right-click in IDEA `build.gradle` of your project, and select `Link Gradle Project`, after completion, hit `Refresh All` in the gradle tab on the right. 6. Run `gradlew runClient` and `gradlew runServer`, or use the auto-imported run configurations in IntelliJ like `1. Run Client`. + +### Mixins: + +- When writing Mixins on IntelliJ, it is advisable to use latest [MinecraftDev Fork for RetroFuturaGradle](https://github.com/eigenraven/MinecraftDev/releases). diff --git a/build.gradle b/build.gradle index ecadeb0..9ba85ba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,34 +1,67 @@ -import com.gtnewhorizons.retrofuturagradle.mcp.ReobfuscatedJar +/** + * It is advised that you do not edit anything in the build.gradle; unless you are sure of what you are doing + */ +import com.gtnewhorizons.retrofuturagradle.mcp.InjectTagsTask +import org.jetbrains.changelog.Changelog import org.jetbrains.gradle.ext.Gradle plugins { - id("java") - id("java-library") - id("maven-publish") - id("org.jetbrains.gradle.plugin.idea-ext") version "1.1.7" - id("eclipse") - id("com.gtnewhorizons.retrofuturagradle") version "1.3.9" - id("com.matthewprenger.cursegradle") version "1.4.0" + id 'java' + id 'java-library' + id 'maven-publish' + id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1.7' + id 'com.gtnewhorizons.retrofuturagradle' version '1.3.35' + id 'com.matthewprenger.cursegradle' version '1.4.0' apply false + id 'com.modrinth.minotaur' version '2.+' apply false + id 'org.jetbrains.changelog' version '2.2.0' } -version = project.mod_version -group = project.maven_group -archivesBaseName = project.archives_base_name +apply from: 'gradle/scripts/helpers.gradle' + +// Early Assertions +assertProperty 'mod_version' +assertProperty 'root_package' +assertProperty 'mod_id' +assertProperty 'mod_name' + +assertSubProperties 'use_tags', 'tag_class_name' +assertSubProperties 'use_access_transformer', 'access_transformer_locations' +assertSubProperties 'use_mixins', 'mixin_booter_version', 'mixin_refmap' +assertSubProperties 'is_coremod', 'coremod_includes_mod', 'coremod_plugin_class_name' +assertSubProperties 'use_asset_mover', 'asset_mover_version' + +setDefaultProperty 'use_modern_java_syntax', false, false +setDefaultProperty 'generate_sources_jar', true, false +setDefaultProperty 'generate_javadocs_jar', true, false +setDefaultProperty 'mapping_channel', true, 'stable' +setDefaultProperty 'mapping_version', true, '39' +setDefaultProperty 'use_dependency_at_files', true, true +setDefaultProperty 'minecraft_username', true, 'Developer' +setDefaultProperty 'extra_jvm_args', false, '' +setDefaultProperty 'extra_tweak_classes', false, '' +setDefaultProperty 'change_minecraft_sources', false, false + +version = propertyString('mod_version') +group = propertyString('root_package') + +base { + archivesName.set(propertyString('mod_id')) +} + +tasks.decompressDecompiledSources.enabled !propertyBool('change_minecraft_sources') -// Set the toolchain version to decouple the Java we run Gradle with from the Java used to compile and run the mod java { toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(propertyBool('use_modern_java_syntax') ? 16 : 8)) // Azul covers the most platforms for Java 8 toolchains, crucially including MacOS arm64 - vendor.set(org.gradle.jvm.toolchain.JvmVendorSpec.AZUL) + vendor.set(JvmVendorSpec.AZUL) + } + if (propertyBool('generate_sources_jar')) { + withSourcesJar() + } + if (propertyBool('generate_javadocs_jar')) { + withJavadocJar() } - // Generate sources and javadocs jars when building and publishing - withSourcesJar() - withJavadocJar() -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = "UTF-8" } configurations { @@ -37,131 +70,139 @@ configurations { } minecraft { - mcVersion = '1.12.2' - def args = ["-ea:${project.group}"] - if (project.use_coremod.toBoolean()) { - args << '-Dfml.coreMods.load=' + coremod_plugin_class_name - } - if (project.use_mixins.toBoolean()) { + mcVersion.set('1.12.2') + + mcpMappingChannel.set(propertyString('mapping_channel')) + mcpMappingVersion.set(propertyString('mapping_version')) + + useDependencyAccessTransformers.set(propertyBool('use_dependency_at_files')) + + username.set(propertyString('minecraft_username')) + + // Add any additional tweaker classes here + extraTweakClasses.addAll(propertyStringList('extra_tweak_classes')) + + // Add various JVM arguments here for runtime + def args = ['-ea:' + group] + if (propertyBool('use_mixins')) { args << '-Dmixin.hotSwap=true' args << '-Dmixin.checks.interfaces=true' args << '-Dmixin.debug.export=true' } extraRunJvmArguments.addAll(args) + extraRunJvmArguments.addAll(propertyStringList('extra_jvm_args')) - useDependencyAccessTransformers = true - - injectedTags.put("VERSION", project.version) -} - -// Generate a my.project.Tags class with the version number as a field -tasks.injectTags.configure { - outputClassName.set("${project.group}.Tags") + if (propertyBool('use_tags')) { + if (file('tags.properties').exists()) { + Properties props = new Properties().tap { it.load(file('tags.properties').newInputStream()); it } + if (!props.isEmpty()) { + injectedTags.set(props.collectEntries { k, v -> [(k): interpolate(v)] }) + } + } + } } repositories { maven { - url = 'https://maven.cleanroommc.com' + name 'CleanroomMC Maven' + url 'https://maven.cleanroommc.com' } - maven { url = "https://repo.spongepowered.org/maven" } - //maven { url "https://maven.mcmoddev.com/" } - maven { - url "https://cursemaven.com" - content { - includeGroup "curse.maven" - } - } - mavenLocal() // Must be last for caching to work } dependencies { - if (project.use_assetmover.toBoolean()) { - implementation 'com.cleanroommc:assetmover:2.0' - } - if (project.use_mixins.toBoolean()) { - implementation 'zone.rong:mixinbooter:7.0' - } - - // Example deobf dependency - // compileOnly rfg.deobf("curse.maven:endercore-231868:2972849:") - - if (project.use_mixins.toBoolean()) { - api ("org.spongepowered:mixin:0.8.3") {transitive = false} - annotationProcessor('org.ow2.asm:asm-debug-all:5.2') - annotationProcessor('com.google.guava:guava:24.1.1-jre') - annotationProcessor('com.google.code.gson:gson:2.8.6') - annotationProcessor ("org.spongepowered:mixin:0.8.3") {transitive = false} - } - -} - -def mixinConfigRefMap = 'mixins.' + project.archives_base_name + '.refmap.json' -def mixinTmpDir = buildDir.path + File.separator + 'tmp' + File.separator + 'mixins' -def refMap = "${mixinTmpDir}" + File.separator + mixinConfigRefMap -def mixinSrg = "${mixinTmpDir}" + File.separator + "mixins.srg" - -if (project.use_mixins.toBoolean()) { - tasks.named("reobfJar", ReobfuscatedJar).configure { - extraSrgFiles.from(mixinSrg) - } - - tasks.named("compileJava", JavaCompile).configure { - doFirst { - new File(mixinTmpDir).mkdirs() + if (propertyBool('use_modern_java_syntax')) { + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0' + // Workaround for https://github.com/bsideup/jabel/issues/174 + annotationProcessor 'net.java.dev.jna:jna-platform:5.13.0' + compileOnly ('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') { + transitive = false } - options.compilerArgs += [ - "-AreobfSrgFile=${tasks.reobfJar.srg.get().asFile}", - "-AoutSrgFile=${mixinSrg}", - "-AoutRefMapFile=${refMap}", - ] + // Allow jdk.unsupported classes like sun.misc.Unsafe, workaround for JDK-8206937 and fixes crashes in tests + patchedMinecraft 'me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0' + // Include for tests + testAnnotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0' + testCompileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') { + transitive = false // We only care about the 1 annotation class + } + } + if (propertyBool('use_asset_mover')) { + implementation "com.cleanroommc:assetmover:${propertyString('asset_mover_version')}" + } + if (propertyBool('use_mixins')) { + String mixin = modUtils.enableMixins("zone.rong:mixinbooter:${propertyString('mixin_booter_version')}", propertyString('mixin_refmap')) + api (mixin) { + transitive = false + } + annotationProcessor 'org.ow2.asm:asm-debug-all:5.2' + annotationProcessor 'com.google.guava:guava:24.1.1-jre' + annotationProcessor 'com.google.code.gson:gson:2.8.6' + annotationProcessor (mixin) { + transitive = false + } + } + if (propertyBool('enable_junit_testing')) { + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } } -if (project.use_access_transformer.toBoolean()) { - for (File at : sourceSets.getByName("main").resources.files) { - if (at.name.toLowerCase().endsWith("_at.cfg")) { - tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(at) - tasks.srgifyBinpatchedJar.accessTransformerFiles.from(at) +apply from: 'gradle/scripts/dependencies.gradle' + +// Adds Access Transformer files to tasks +if (propertyBool('use_access_transformer')) { + for (def location : propertyStringList('access_transformer_locations')) { + def fileLocation = file("${projectDir}/src/main/resources/${location}") + if (fileLocation.exists()) { + tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(fileLocation) + tasks.srgifyBinpatchedJar.accessTransformerFiles.from(fileLocation) + } else { + throw new GradleException("Access Transformer file [$fileLocation] does not exist!") } } } processResources { - // this will ensure that this task is redone when the versions change. - inputs.property 'version', project.version - inputs.property 'mcversion', project.minecraft.version - // replace stuff in mcmod.info, nothing else - filesMatching(['mcmod.info', 'pack.mcmeta']) { fcd -> - // replace version and mcversion - fcd.expand ( - 'version': project.version, - 'mcversion': project.minecraft.version + + def filterList = ['mcmod.info', 'pack.mcmeta'] + filterList.addAll(propertyStringList('mixin_configs').collect(config -> "mixins.${config}.json" as String)) + + filesMatching(filterList) { fcd -> + fcd.expand( + 'mod_id': propertyString('mod_id'), + 'mod_name': propertyString('mod_name'), + 'mod_version': propertyString('mod_version'), + 'mod_description': propertyString('mod_description'), + 'mod_authors': "[${propertyStringList('mod_authors', ',').join(', ')}]", + 'mod_credits': propertyString('mod_credits'), + 'mod_url': propertyString('mod_url'), + 'mod_update_json': propertyString('mod_update_json'), + 'mod_logo_path': propertyString('mod_logo_path'), + 'mixin_refmap': propertyString('mixin_refmap'), + 'mixin_package': propertyString('mixin_package') ) } - if (project.use_access_transformer.toBoolean()) { - rename '(.+_at.cfg)', 'META-INF/$1' // Access Transformers + if (propertyBool('use_access_transformer')) { + rename '(.+_at.cfg)', 'META-INF/$1' } - if (project.use_mixins.toBoolean()) { - // Embed mixin refmap - from refMap - dependsOn("compileJava") - } } jar { manifest { def attribute_map = [:] - if (project.use_coremod.toBoolean()) { - attribute_map['FMLCorePlugin'] = project.coremod_plugin_class_name - if (project.include_mod.toBoolean()) { + if (propertyBool('is_coremod')) { + attribute_map['FMLCorePlugin'] = propertyString('coremod_plugin_class_name') + if (propertyBool('coremod_includes_mod')) { attribute_map['FMLCorePluginContainsFMLMod'] = true - attribute_map['ForceLoadAsMod'] = project.gradle.startParameter.taskNames[0] == "build" + def currentTasks = gradle.startParameter.taskNames + if (currentTasks[0] == 'build' || currentTasks[0] == 'prepareObfModsFolder' || currentTasks[0] == 'runObfClient') { + attribute_map['ForceLoadAsMod'] = true + } } } - if (project.use_access_transformer.toBoolean()) { - attribute_map['FMLAT'] = project.archives_base_name + '_at.cfg' + if (propertyBool('use_access_transformer')) { + attribute_map['FMLAT'] = propertyString('access_transformer_locations') } attributes(attribute_map) } @@ -170,33 +211,136 @@ jar { } idea { - module { inheritOutputDirs = true } - project { settings { - runConfigurations { - "1. Run Client"(Gradle) { - taskNames = ["runClient"] + module { + inheritOutputDirs = true + } + project { + settings { + runConfigurations { + "1. Run Client"(Gradle) { + taskNames = ["runClient"] + } + "2. Run Server"(Gradle) { + taskNames = ["runServer"] + } + "3. Run Obfuscated Client"(Gradle) { + taskNames = ["runObfClient"] + } + "4. Run Obfuscated Server"(Gradle) { + taskNames = ["runObfServer"] + } } - "2. Run Server"(Gradle) { - taskNames = ["runServer"] - } - "3. Run Obfuscated Client"(Gradle) { - taskNames = ["runObfClient"] - } - "4. Run Obfuscated Server"(Gradle) { - taskNames = ["runObfServer"] + compiler.javac { + afterEvaluate { + javacAdditionalOptions = "-encoding utf8" + moduleJavacAdditionalOptions = [ + (project.name + ".main"): tasks.compileJava.options.compilerArgs.collect { '"' + it + '"' }.join(' ') + ] + } } } - compiler.javac { - afterEvaluate { - javacAdditionalOptions = "-encoding utf8" - moduleJavacAdditionalOptions = [ - (project.name + ".main"): tasks.compileJava.options.compilerArgs.collect { '"' + it + '"' }.join(' ') - ] - } - } - }} + } } -tasks.named("processIdeaSettings").configure { - dependsOn("injectTags") +compileTestJava { + sourceCompatibility = targetCompatibility = 8 } + +test { + useJUnitPlatform() + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + }) + if (propertyBool('show_testing_output')) { + testLogging { + showStandardStreams = true + } + } +} + +String parserChangelog() { + if (!file('CHANGELOG.md').exists()) { + throw new GradleException('publish_with_changelog is true, but CHANGELOG.md does not exist in the workspace!') + } + String parsedChangelog = changelog.renderItem( + changelog.get(propertyString('mod_version')).withHeader(false).withEmptySections(false), + Changelog.OutputType.MARKDOWN) + if (parsedChangelog.isEmpty()) { + throw new GradleException('publish_with_changelog is true, but the changelog for the latest version is empty!') + } + return parsedChangelog +} + +tasks.register('generateMixinJson') { + group 'cleanroom helpers' + def missingConfig = propertyStringList('mixin_configs').findAll(config -> !file("src/main/resources/mixins.${config}.json").exists()) + onlyIf { + if (propertyBool('use_mixins') && propertyBool('generate_mixins_json')) { + return !missingConfig.empty + } + return false + } + doLast { + for (String mixinConfig : missingConfig) { + def file = file("src/main/resources/mixins.${mixinConfig}.json") + file << """{\n\t"package": "",\n\t"required": true,\n\t"refmap": "${mixin_refmap}",\n\t"target": "@env(DEFAULT)",\n\t"minVersion": "0.8.5",\n\t"compatibilityLevel": "JAVA_8",\n\t"mixins": [],\n\t"server": [],\n\t"client": []\n}""" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + if (propertyBool('use_modern_java_syntax')) { + if (it.name in ['compileMcLauncherJava', 'compilePatchedMcJava']) { + return + } + sourceCompatibility = 17 + options.release.set(8) + javaCompiler.set(javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(16)) + vendor.set(JvmVendorSpec.AZUL) + }) + } +} + +tasks.register('cleanroomAfterSync') { + group 'cleanroom helpers' + dependsOn 'injectTags', 'generateMixinJson' +} + +if (propertyBool('use_modern_java_syntax')) { + tasks.withType(Javadoc).configureEach { + sourceCompatibility = 17 + } +} + +tasks.named('injectTags', InjectTagsTask).configure { + onlyIf { + return propertyBool('use_tags') && !it.getTags().get().isEmpty() + } + it.outputClassName.set(propertyString('tag_class_name')) +} + +tasks.named('prepareObfModsFolder').configure { + finalizedBy 'prioritizeCoremods' +} + +tasks.register('prioritizeCoremods') { + dependsOn 'prepareObfModsFolder' + doLast { + fileTree('run/obfuscated').forEach { + if (it.isFile() && it.name =~ '(mixinbooter|configanytime)(-)([0-9])+\\.+([0-9])+(.jar)') { + it.renameTo(new File(it.parentFile, "!${it.name}")) + } + } + } +} + +idea.project.settings { + taskTriggers { + afterSync 'cleanroomAfterSync' + } +} + +apply from: 'gradle/scripts/publishing.gradle' +apply from: 'gradle/scripts/extra.gradle' diff --git a/gradle.properties b/gradle.properties index c8318b8..3a3daea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,126 @@ -# 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. +# Gradle Properties org.gradle.jvmargs = -Xmx3G +# Source Options +# Use Modern Java(9+) Syntax (Courtesy of Jabel) +use_modern_java_syntax = false + +# Compilation Options +generate_sources_jar = true +generate_javadocs_jar = false + +# Testing +enable_junit_testing = true +show_testing_output = false + # Mod Information -mod_version = 1.0 -maven_group = com.cleanroommc -archives_base_name = modid +# HIGHLY RECOMMEND complying with SemVer for mod_version: https://semver.org/ +mod_version = 1.0.0 +root_package = ru.octol1ttle +mod_id = knockdowns +mod_name = Knockdowns (Legacy) -# If any properties changes below this line, run `gradlew setupDecompWorkspace` and refresh gradle again to ensure everything is working correctly. +# Mod Metadata (Optional) +mod_description = +mod_url = +mod_update_json = +# Delimit authors with commas +mod_authors = Octol1ttle +mod_credits = +mod_logo_path = -# Boilerplate Options -use_mixins = false -use_coremod = false -use_assetmover = false +# Mapping Properties +mapping_channel = stable +mapping_version = 39 +use_dependency_at_files = true -# Access Transformer files should be in the root of `resources` folder and with the filename formatted as: `{archives_base_name}_at.cfg` +# Run Configurations +# If multiple arguments/tweak classes are stated, use spaces as the delimiter +minecraft_username = Developer +extra_jvm_args = +extra_tweak_classes = + +# Maven Publishing (Provide secret: MAVEN_USER, MAVEN_PASS) +publish_to_maven = false +# Good for debugging artifacts before uploading to remote maven +# GitHub actions won't run if this is true, test this by running the task `publishToMavenLocal` +publish_to_local_maven = false +maven_name = ${mod_name} +maven_url = + +# Publishing +# release_type can only be: release, beta or alpha (applies to CurseForge / Modrinth) +release_type = release +publish_with_changelog = ${{ it.file('CHANGELOG.md').exists() }} + +# Publishing to CurseForge (Provide secret: CURSEFORGE_TOKEN) +# To configure dependencies, head to publishing.gradle's curseforge block +publish_to_curseforge = false +# CurseForge project ID must be the numerical ID and not the slug +curseforge_project_id = +curseforge_debug = false + +# Publishing to Modrinth (Provide secret: MODRINTH_TOKEN), the token must have the `CREATE_VERSION` and `PROJECT_WRITE` permissions +# To configure dependencies, head to publishing.gradle's modrinth block +publish_to_modrinth = false +modrinth_project_id = +# Allows gradle to publish updated READMEs to the project body (via the modrinthSyncBody task) +modrinth_sync_readme = false +modrinth_debug = false + +# If any properties changes below this line, refresh gradle again to ensure everything is working correctly. + +# Modify Minecraft Sources +# RetroFuturaGradle allows Minecraft sources to be edited, and have the changes reflected upon running it +# Good for previews when coremodding, or generally seeing how behaviours can change with certain code applied/unapplied +# Turning this on allows Minecraft sources to persist and not regenerate +change_minecraft_sources = false + +# Tags +# A RetroFuturaGradle concept akin to Ant ReplaceTokens +# A class is generated at build-time for compilation, to describe properties that have values that could change at build time such as versioning +# Class name is configurable with the `tag_class_name` property +# Tag properties can be stated in the `tags.properties` file, references are allowed +use_tags = true +tag_class_name = ${root_package}.${mod_id}.Tags + +# Access Transformers +# A way to change visibility of Minecraft's classes, methods and fields +# An example access transformer file is given in the path: `src/main/resources/example_at.cfg` +# AT files should be in the root of src/main/resources with the filename formatted as: `mod_id_at.cfg` +# Use the property `access_transformer_locations` to state custom AT files if you aren't using the default `mod_id_at.cfg` location +# If multiple locations are stated, use spaces as the delimiter use_access_transformer = false +access_transformer_locations = ${mod_id}_at.cfg -# Coremod Arguments -include_mod = true -coremod_plugin_class_name = \ No newline at end of file +# Mixins +# Powerful tool to do runtime description changes of classes +# Wiki: https://github.com/SpongePowered/Mixin/wiki + https://github.com/CleanroomMC/MixinBooter/ + https://cleanroommc.com/wiki/forge-mod-development/mixin/preface +# Only use mixins once you understand the underlying structure +use_mixins = true +mixin_booter_version = 9.1 +# A configuration defines a mixin set, and you may have as many mixin sets as you require for your application. +# Each config can only have one and only one package root. +# Generate missing configs, obtain from mixin_configs and generate file base on name convention: "mixins.config_name.json" +# You should change package root once they are generated +generate_mixins_json = true +# Delimit configs with spaces. Should only put configs name instead of full file name +mixin_configs = ${mod_id} +# A refmap is a json that denotes mapping conversions, this json is generated automatically, with the name `mixins.mod_id.refmap.json` +# Use the property `mixin_refmap` if you want it to use a different name, only one name is accepted +mixin_refmap = mixins.${mod_id}.refmap.json + +# Coremods +# The most powerful way to change java classes at runtime, it is however very primitive with little documentation. +# Only make a coremod if you are absolutely sure of what you are doing +# Change the property `coremod_includes_mod` to false if your coremod doesn't have a @Mod annotation +# You MUST state a class name for `coremod_plugin_class_name` if you are making a coremod, the class should implement `IFMLLoadingPlugin` +is_coremod = true +coremod_includes_mod = true +coremod_plugin_class_name = ru.octol1ttle.knockdowns.common.KnockdownsFMLLoadingPlugin + +# AssetMover +# Convenient way to allow downloading of assets from official vanilla Minecraft servers, CurseForge, or any direct links +# Documentation: https://github.com/CleanroomMC/AssetMover +use_asset_mover = false +asset_mover_version = 2.5 diff --git a/gradle/scripts/dependencies.gradle b/gradle/scripts/dependencies.gradle new file mode 100644 index 0000000..557c777 --- /dev/null +++ b/gradle/scripts/dependencies.gradle @@ -0,0 +1,62 @@ +apply from: 'gradle/scripts/helpers.gradle' + +repositories { + // Other repositories described by default: + // CleanroomMC: https://maven.cleanroommc.com + exclusiveContent { + forRepository { + maven { + name 'CurseMaven' + url 'https://cursemaven.com' + } + } + filter { + includeGroup 'curse.maven' + } + } + exclusiveContent { + forRepository { + maven { + name 'Modrinth' + url 'https://api.modrinth.com/maven' + } + } + filter { + includeGroup 'maven.modrinth' + } + } + mavenLocal() // Must be last for caching to work +} + +dependencies { + // Example - Dependency descriptor: + // 'com.google.code.gson:gson:2.8.6' << group: com.google.code.gson, name:gson, version:2.8.6 + // 'group:name:version:classifier' where classifier is optional + + // Example - Deobfuscating dependencies: + // rfg.deobf('curse.maven:had-enough-items-557549:4543375') + // By wrapping a dependency descriptor in rfg.deobf() method call, the dependency is queued for deobfuscation + // When deobfuscating, RFG respects the mapping_channel + mapping_version stated in gradle.properties + + // Example - CurseMaven dependencies: + // 'curse.maven:had-enough-items-557549:4543375' << had-enough-items = project slug, 557549 = project id, 4543375 = file id + // Full documentation: https://cursemaven.com/ + + // Example - Modrinth dependencies: + // 'maven.modrinth:jei:4.16.1.1000' << jei = project name, 4.16.1.1000 = file version + // Full documentation: https://docs.modrinth.com/docs/tutorials/maven/ + + // Common dependency types (configuration): + // implementation = dependency available at both compile time and runtime + // runtimeOnly = runtime dependency + // compileOnly = compile time dependency + // annotationProcessor = annotation processing dependencies + + // Transitive dependencies: + // (Dependencies that your dependency depends on) + // If you wish to exclude transitive dependencies in the described dependencies + // Use a closure as such: + // implementation ('com.google.code.gson:gson:2.8.6') { + // transitive = false + // } +} \ No newline at end of file diff --git a/gradle/scripts/extra.gradle b/gradle/scripts/extra.gradle new file mode 100644 index 0000000..a44cb8e --- /dev/null +++ b/gradle/scripts/extra.gradle @@ -0,0 +1,5 @@ +// You may write any gradle buildscript component in this file +// This file is automatically applied after build.gradle + dependencies.gradle is ran + +// If you wish to use the default helper methods, uncomment the line below +// apply from: 'gradle/scripts/helpers.gradle' diff --git a/gradle/scripts/helpers.gradle b/gradle/scripts/helpers.gradle new file mode 100644 index 0000000..0b3f2ee --- /dev/null +++ b/gradle/scripts/helpers.gradle @@ -0,0 +1,96 @@ +import groovy.text.SimpleTemplateEngine +import org.codehaus.groovy.runtime.MethodClosure + +ext.propertyString = this.&propertyString as MethodClosure +ext.propertyBool = this.&propertyBool as MethodClosure +ext.propertyStringList = this.&propertyStringList as MethodClosure +ext.interpolate = this.&interpolate as MethodClosure +ext.assertProperty = this.&assertProperty as MethodClosure +ext.assertSubProperties = this.&assertSubProperties as MethodClosure +ext.setDefaultProperty = this.&setDefaultProperty as MethodClosure +ext.assertEnvironmentVariable = this.&assertEnvironmentVariable as MethodClosure + +String propertyString(String key) { + return $property(key).toString() +} + +boolean propertyBool(String key) { + return propertyString(key).toBoolean() +} + +Collection propertyStringList(String key) { + return propertyStringList(key, ' ') +} + +Collection propertyStringList(String key, String delimit) { + return propertyString(key).split(delimit).findAll { !it.isEmpty() } +} + +private Object $property(String key) { + def value = project.findProperty(key) + if (value instanceof String) { + return interpolate(value) + } + return value +} + +String interpolate(String value) { + if (value.startsWith('${{') && value.endsWith('}}')) { + value = value.substring(3, value.length() - 2) + Binding newBinding = new Binding(this.binding.getVariables()) + newBinding.setProperty('it', this) + return new GroovyShell(this.getClass().getClassLoader(), newBinding).evaluate(value) + } + if (value.contains('${')) { + return new SimpleTemplateEngine().createTemplate(value).make(project.properties).toString() + } + return value +} + +void assertProperty(String propertyName) { + def property = property(propertyName) + if (property == null) { + throw new GradleException("Property ${propertyName} is not defined!") + } + if (property.isEmpty()) { + throw new GradleException("Property ${propertyName} is empty!") + } +} + +void assertSubProperties(String propertyName, String... subPropertyNames) { + assertProperty(propertyName) + if (propertyBool(propertyName)) { + for (String subPropertyName : subPropertyNames) { + assertProperty(subPropertyName) + } + } +} + +void setDefaultProperty(String propertyName, boolean warn, defaultValue) { + def property = property(propertyName) + def exists = true + if (property == null) { + exists = false + if (warn) { + project.logger.log(LogLevel.WARN, "Property ${propertyName} is not defined!") + } + } else if (property.isEmpty()) { + exists = false + if (warn) { + project.logger.log(LogLevel.WARN, "Property ${propertyName} is empty!") + } + } + if (!exists) { + project.setProperty(propertyName, defaultValue.toString()) + } +} + +void assertEnvironmentVariable(String propertyName) { + def property = System.getenv(propertyName) + if (property == null) { + throw new GradleException("System Environment Variable $propertyName is not defined!") + } + if (property.isEmpty()) { + throw new GradleException("Property $propertyName is empty!") + } +} diff --git a/gradle/scripts/publishing.gradle b/gradle/scripts/publishing.gradle new file mode 100644 index 0000000..c7897c9 --- /dev/null +++ b/gradle/scripts/publishing.gradle @@ -0,0 +1,107 @@ +apply from: 'gradle/scripts/helpers.gradle' + +setDefaultProperty('publish_to_maven', true, false) +setDefaultProperty('publish_to_curseforge', true, false) +setDefaultProperty('publish_to_modrinth', true, false) + +if (propertyBool('publish_to_maven')) { + assertProperty('maven_name') + assertProperty('maven_url') + publishing { + repositories { + maven { + name propertyString('maven_name').replaceAll("\\s", "") + url propertyString('maven_url') + credentials(PasswordCredentials) + } + } + publications { + mavenJava(MavenPublication) { + from components.java // Publish with standard artifacts + setGroupId(propertyString('root_package'))// Publish with root package as maven group + setArtifactId(propertyString('mod_id')) // Publish artifacts with mod id as the artifact id + + // Custom artifact: + // If you want to publish a different artifact to the one outputted when building normally + // Create a different gradle task (Jar task), in extra.gradle + // Remove the 'from components.java' line above + // Add this line (change the task name): + // artifacts task_name + } + } + } +} + +// Documentation here: https://github.com/matthewprenger/CurseGradle/wiki/ +if (propertyBool('publish_to_curseforge')) { + apply plugin: 'com.matthewprenger.cursegradle' + assertProperty('curseforge_project_id') + assertProperty('release_type') + setDefaultProperty('curseforge_debug', false, false) + curseforge { + apiKey = System.getenv('CURSEFORGE_TOKEN') == null ? "" : System.getenv('CURSEFORGE_TOKEN') + // noinspection GroovyAssignabilityCheck + project { + id = propertyString('curseforge_project_id') + addGameVersion 'Java 8' + addGameVersion 'Forge' + addGameVersion '1.12.2' + releaseType = propertyString('release_type') + if (!propertyBool('publish_with_changelog')) { + changelog = parserChangelog() + changelogType = 'markdown' + } + mainArtifact tasks.reobfJar, { + displayName = "${propertyString('mod_name')} ${propertyString('mod_version')}" + if (propertyBool('use_mixins')) { + relations { + requiredDependency 'mixin-booter' + } + } + if (propertyBool('use_asset_mover')) { + relations { + requiredDependency 'assetmover' + } + } + } + options { + debug = propertyBool('curseforge_debug') + } + } + } +} + +// Documentation here: https://github.com/modrinth/minotaur +if (propertyBool('publish_to_modrinth')) { + apply plugin: 'com.modrinth.minotaur' + assertProperty('modrinth_project_id') + assertProperty('release_type') + setDefaultProperty('modrinth_debug', false, false) + modrinth { + token = System.getenv('MODRINTH_TOKEN') ? "" : System.getenv('MODRINTH_TOKEN') + projectId = propertyString('modrinth_project_id') + versionNumber = propertyString('mod_version') + versionType = propertyString('release_type') + uploadFile = tasks.reobfJar + gameVersions = ['1.12.2'] + loaders = ['forge'] + debugMode = propertyBool('modrinth_debug') + if (propertyBool('use_mixins') || propertyBool('use_asset_mover')) { + dependencies { + if (propertyBool('use_mixins')) { + required.project 'mixinbooter' + } + if (propertyBool('use_asset_mover')) { + required.project 'assetmover' + } + } + } + if (!propertyBool('publish_with_changelog')) { + changelog = parserChangelog() + } + if (propertyBool('modrinth_sync_readme')) { + syncBodyFrom = file('README.md').text + tasks.modrinth.dependsOn(tasks.modrinthSyncBody) + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..20db9ad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 112969a..2c64da6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,12 +2,11 @@ pluginManagement { repositories { maven { // RetroFuturaGradle - name = "GTNH Maven" - url = uri("http://jenkins.usrv.eu:8081/nexus/content/groups/public/") - allowInsecureProtocol = true + name 'GTNH Maven' + url 'https://nexus.gtnewhorizons.com/repository/public/' mavenContent { - includeGroup("com.gtnewhorizons") - includeGroup("com.gtnewhorizons.retrofuturagradle") + includeGroup 'com.gtnewhorizons' + includeGroup 'com.gtnewhorizons.retrofuturagradle' } } gradlePluginPortal() @@ -18,7 +17,9 @@ pluginManagement { plugins { // Automatic toolchain provisioning - id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' } -rootProject.name = archives_base_name +// Due to an IntelliJ bug, this has to be done +// rootProject.name = archives_base_name +rootProject.name = rootProject.projectDir.getName() diff --git a/src/main/java/com/cleanroommc/README.md b/src/main/java/com/cleanroommc/README.md deleted file mode 100644 index de1e64c..0000000 --- a/src/main/java/com/cleanroommc/README.md +++ /dev/null @@ -1,2 +0,0 @@ -- Here lies the root of the `io.github.cleanroommc` package, add another level with your mod id and use that as the root for your mod classes. - diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java b/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java new file mode 100644 index 0000000..80a6603 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java @@ -0,0 +1,113 @@ +package ru.octol1ttle.knockdowns.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.event.KnockdownsKeyListener; +import ru.octol1ttle.knockdowns.client.util.DirectionalCallSound; +import ru.octol1ttle.knockdowns.common.IClientProxy; +import ru.octol1ttle.knockdowns.common.KnockdownsMod; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; + +@SideOnly(Side.CLIENT) +public class ClientProxy implements IClientProxy { + private static final Minecraft client = Minecraft.getMinecraft(); + + @Override + public void onFMLInit(FMLInitializationEvent event) { + KnockdownsMod.LOGGER.info("Registering key bindings"); + KnockdownsKeyListener.registerKeyBindings(); + } + + @Override + public T handleMessage(IMessage message) { + if (message instanceof PlayerCalloutS2CPacket) { + PlayerCalloutS2CPacket packet = (PlayerCalloutS2CPacket) message; + client.addScheduledTask(() -> { + if (CalloutManager.addOrUpdateCallout(packet)) { + CalloutManager.playCalloutSound(packet); + } + }); + } else if (message instanceof PlayerKnockedDownS2CPacket) { + PlayerKnockedDownS2CPacket packet = (PlayerKnockedDownS2CPacket) message; + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(packet.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(true); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.getRevivers().clear(); + } + + if (client.player.dimension == packet.dimensionId) { + client.getSoundHandler().playSound(new DirectionalCallSound(KnockdownsSoundEvents.KNOCKED_DOWN, entity, packet.position)); + KnockedNotificationManager.addKnockedNotification(packet.playerId, packet.position); + } + }); + } else if (message instanceof SynchronizePlayerDataS2CPacket.KnockedDown) { + SynchronizePlayerDataS2CPacket.KnockedDown packet = (SynchronizePlayerDataS2CPacket.KnockedDown) message; + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(packet.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(packet.knockedDown); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.getRevivers().clear(); + } + }); + } else if (message instanceof SynchronizePlayerDataS2CPacket.ReviveTimeLeft) { + SynchronizePlayerDataS2CPacket.ReviveTimeLeft packet = (SynchronizePlayerDataS2CPacket.ReviveTimeLeft) message; + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(packet.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setReviveTimeLeft(packet.reviveTimeLeft); + } + }); + } else if (message instanceof SynchronizePlayerDataS2CPacket.Full) { + SynchronizePlayerDataS2CPacket.Full packet = (SynchronizePlayerDataS2CPacket.Full) message; + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(packet.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(packet.knockedDown); + data.setReviveTimeLeft(packet.reviveTimeLeft); + } + }); + } else if (message instanceof SynchronizeReviversS2CPacket.Add) { + SynchronizeReviversS2CPacket.Add packet = (SynchronizeReviversS2CPacket.Add) message; + client.addScheduledTask(() -> { + EntityPlayer knocked = (EntityPlayer) client.world.getEntityByID(packet.knockedId); + EntityPlayer reviver = (EntityPlayer) client.world.getEntityByID(packet.reviverId); + if (knocked != null && reviver != null) { + IKnockdownsPlayerData.get(knocked).getRevivers().add(reviver); + } + }); + } else if (message instanceof SynchronizeReviversS2CPacket.Remove) { + SynchronizeReviversS2CPacket.Remove packet = (SynchronizeReviversS2CPacket.Remove) message; + client.addScheduledTask(() -> { + EntityPlayer knocked = (EntityPlayer) client.world.getEntityByID(packet.knockedId); + EntityPlayer reviver = (EntityPlayer) client.world.getEntityByID(packet.reviverId); + if (knocked != null && reviver != null) { + IKnockdownsPlayerData.get(knocked).getRevivers().remove(reviver); + } + }); + } else { + throw new IllegalStateException("Unknown packet received on the client: " + message.getClass().getName()); + } + + return null; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java b/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java new file mode 100644 index 0000000..a6ecefe --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java @@ -0,0 +1,34 @@ +package ru.octol1ttle.knockdowns.client.communication; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.util.Callout; +import ru.octol1ttle.knockdowns.client.util.DirectionalCallSound; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +@SideOnly(Side.CLIENT) +public class CalloutManager { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final Map callouts = new HashMap<>(); + + public static Set> getCallouts() { + return callouts.entrySet(); + } + + public static boolean addOrUpdateCallout(PlayerCalloutS2CPacket message) { + return callouts.put(message.playerId, new Callout(message.position, message.type, client.world.getTotalWorldTime())) == null; + } + + public static void playCalloutSound(PlayerCalloutS2CPacket message) { + client.getSoundHandler().playSound(new DirectionalCallSound(KnockdownsSoundEvents.CALLOUT, client.world.getEntityByID(message.playerId), message.position)); + } + + public static void clearCallouts() { + callouts.clear(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java b/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java new file mode 100644 index 0000000..923e768 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java @@ -0,0 +1,28 @@ +package ru.octol1ttle.knockdowns.client.communication; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.util.KnockedPlayerData; + +@SideOnly(Side.CLIENT) +public class KnockedNotificationManager { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final List knockedDatas = new ArrayList<>(); + + public static void addKnockedNotification(int playerId, Vec3d position) { + knockedDatas.add(new KnockedPlayerData(playerId, position, client.world.getTotalWorldTime())); + } + + public static Collection getKnockedPlayerDatas() { + return knockedDatas; + } + + public static void clearDatas() { + knockedDatas.clear(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java new file mode 100644 index 0000000..0aa8db7 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java @@ -0,0 +1,71 @@ +package ru.octol1ttle.knockdowns.client.event; + +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.InputEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.gui.CommunicationGui; +import ru.octol1ttle.knockdowns.client.gui.KnockedNotificationGui; +import ru.octol1ttle.knockdowns.client.gui.ReviveGui; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; + +@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(value = Side.CLIENT, modid = Tags.MOD_ID) +public class KnockdownsClientEventListener { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final CommunicationGui communicationGui = new CommunicationGui(); + private static final KnockedNotificationGui notificationGui = new KnockedNotificationGui(); + private static final ReviveGui reviveGui = new ReviveGui(); + + @SubscribeEvent + public static void onTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + return; + } + if (client.world == null) { + CalloutManager.clearCallouts(); + KnockedNotificationManager.clearDatas(); + return; + } + CalloutManager.getCallouts().removeIf(callout -> client.world.getTotalWorldTime() - callout.getValue().getReceiveTime() > 60); + KnockedNotificationManager.getKnockedPlayerDatas().removeIf(notification -> client.world.getTotalWorldTime() - notification.getReceiveTime() > 100); + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + List revivers = IKnockdownsPlayerData.get(event.player).getRevivers(); + if (revivers.contains(client.player) && !event.player.equals(client.pointedEntity)) { + KnockdownsNetwork.sendToServer(new CancelReviveC2SPacket()); + revivers.remove(client.player); + } + } + + @SubscribeEvent + public static void onRenderWorldLast(RenderWorldLastEvent event) { + notificationGui.renderNotifications(event.getPartialTicks()); + communicationGui.renderCallouts(event.getPartialTicks()); + } + + @SubscribeEvent + public static void onRenderGameOverlay(RenderGameOverlayEvent.Chat event) { + communicationGui.render(event.getPartialTicks(), event.getResolution()); + reviveGui.render(event.getPartialTicks(), event.getResolution()); + } + + @SubscribeEvent + public static void onKeyInput(InputEvent.KeyInputEvent event) { + KnockdownsKeyListener.tickKeys(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java new file mode 100644 index 0000000..e2c1057 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java @@ -0,0 +1,56 @@ +package ru.octol1ttle.knockdowns.client.event; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.KeyBinding; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; + +@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(value = Side.CLIENT, modid = Tags.MOD_ID) +public class KnockdownsKeyListener { + private static final Minecraft client = Minecraft.getMinecraft(); + public static final Map> calloutBindings = new HashMap<>(); + + public static void registerKeyBindings() { + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.danger", Keyboard.KEY_LEFT, "knockdowns.key.category"), + () -> CalloutType.DANGER + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.booyah", Keyboard.KEY_DOWN, "knockdowns.key.category"), + () -> CalloutType.BOOYAH + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.this_way_help", Keyboard.KEY_UP, "knockdowns.key.category"), + () -> IKnockdownsPlayerData.get(client.player).isKnockedDown() ? CalloutType.HELP : CalloutType.THIS_WAY + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.ouch", Keyboard.KEY_RIGHT, "knockdowns.key.category"), + () -> CalloutType.OUCH + ); + + for (KeyBinding binding : calloutBindings.keySet()) { + ClientRegistry.registerKeyBinding(binding); + } + } + + public static void tickKeys() { + for (KeyBinding binding : calloutBindings.keySet()) { + if (binding.isPressed()) { + KnockdownsNetwork.sendToServer(new PlayerCalloutC2SPacket(calloutBindings.get(binding).get())); + break; + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java new file mode 100644 index 0000000..ac1920c --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java @@ -0,0 +1,175 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import java.util.Map; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.event.KnockdownsKeyListener; +import ru.octol1ttle.knockdowns.client.util.Callout; + +@SideOnly(Side.CLIENT) +public class CommunicationGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final ResourceLocation LEFT_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/left_arrow.png"); + private static final ResourceLocation DOWN_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/down_arrow.png"); + private static final ResourceLocation UP_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/up_arrow.png"); + private static final ResourceLocation RIGHT_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/right_arrow.png"); + private static final int SCREEN_EDGE_MARGIN = 5; + private static final int SEPARATOR_MARGIN = 2; + private static final int KEY_SIZE = 17; + private float totalPartialTicks; + + @Override + public void render(float partialTicks, ScaledResolution resolution) { + FontRenderer font = client.fontRenderer; + + int x = SCREEN_EDGE_MARGIN; + int y = resolution.getScaledHeight() - SCREEN_EDGE_MARGIN - font.FONT_HEIGHT; + + KeyBinding[] sortedBindings = new KeyBinding[4]; + for (KeyBinding binding : KnockdownsKeyListener.calloutBindings.keySet()) + { + switch (binding.getKeyCode()) { + case Keyboard.KEY_LEFT: + sortedBindings[0] = binding; + break; + case Keyboard.KEY_DOWN: + sortedBindings[1] = binding; + break; + case Keyboard.KEY_UP: + sortedBindings[2] = binding; + break; + case Keyboard.KEY_RIGHT: + sortedBindings[3] = binding; + break; + } + } + + KeyBinding leftCallout = sortedBindings[0]; + if (leftCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(leftCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x, + y - 12, + 0xFFFFFF + ); + + x += font.getStringWidth(text) + SEPARATOR_MARGIN; + client.getTextureManager().bindTexture(LEFT_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + x += KEY_SIZE + SEPARATOR_MARGIN; + } + + KeyBinding downCallout = sortedBindings[1]; + if (downCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(downCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE * 0.5f - font.getStringWidth(text) * 0.5f, + y + SEPARATOR_MARGIN, + 0xFFFFFF + ); + client.getTextureManager().bindTexture(DOWN_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + } + + KeyBinding upCallout = sortedBindings[2]; + if (upCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(upCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE * 0.5f - font.getStringWidth(text) * 0.5f, + y - KEY_SIZE * 2 - 12, + 0xFFFFFF + ); + + client.getTextureManager().bindTexture(UP_ARROW); + this.drawTexture( + x, + y - KEY_SIZE * 2 - 2, + KEY_SIZE, + KEY_SIZE + ); + } + + KeyBinding rightCallout = sortedBindings[3]; + if (rightCallout != null) { + x += KEY_SIZE + SEPARATOR_MARGIN; + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(rightCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE + SEPARATOR_MARGIN, + y - 12, + 0xFFFFFF + ); + + client.getTextureManager().bindTexture(RIGHT_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + } + } + + public void renderCallouts(float partialTicks) { + totalPartialTicks += partialTicks; + for (Map.Entry calloutEntry : CalloutManager.getCallouts()) { + Entity entity = client.world.getEntityByID(calloutEntry.getKey()); + renderCallout( + I18n.format(calloutEntry.getValue().getType().getTextKey()), + entity != null ? entity.getPositionEyes(partialTicks).add(0, 1, 0) : calloutEntry.getValue().getPosition(), + client.getRenderManager().playerViewX, + client.getRenderManager().playerViewY, + client.getRenderManager().options.thirdPersonView == 2 + ); + } + } + + private void renderCallout(String text, Vec3d position, float pitch, float yaw, boolean isThirdPersonFrontal) { + FontRenderer font = client.fontRenderer; + + Vec3d deltaPos = position.subtract(client.getRenderManager().viewerPosX, client.getRenderManager().viewerPosY, client.getRenderManager().viewerPosZ); + + GlStateManager.pushMatrix(); + GlStateManager.translate(deltaPos.x, deltaPos.y, deltaPos.z); + GlStateManager.rotate(-yaw, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate((float)(isThirdPersonFrontal ? -1 : 1) * pitch, 1.0F, 0.0F, 0.0F); + float scale = (float) Math.max(deltaPos.length() / 8.0f, 1.0f) * (0.75f + MathHelper.abs((float) (MathHelper.sin(totalPartialTicks / 10F) / Math.PI))); + GlStateManager.scale(-0.025F * scale, -0.025F * scale, 0.025F * scale); + GlStateManager.color(1f, 1f, 1f, 1f); + GlStateManager.disableCull(); + GlStateManager.depthFunc(GL11.GL_ALWAYS); + + font.drawStringWithShadow(text, -font.getStringWidth(text) * 0.5f, -font.FONT_HEIGHT * 0.5f, 0xFFFFFF); + + GlStateManager.depthFunc(GL11.GL_LEQUAL); + GlStateManager.enableCull(); + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java new file mode 100644 index 0000000..d2e635e --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java @@ -0,0 +1,25 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public abstract class KnockdownsBaseGui extends Gui { + public abstract void render(float partialTicks, ScaledResolution resolution); + + protected void drawTexture(int x, int y, int width, int height) { + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + bufferbuilder.begin(7, DefaultVertexFormats.POSITION_TEX); + bufferbuilder.pos(x, y, this.zLevel).tex(0, 0).endVertex(); + bufferbuilder.pos(x, y + height, this.zLevel).tex(0, 1).endVertex(); + bufferbuilder.pos(x + width, y + height, this.zLevel).tex(1, 1).endVertex(); + bufferbuilder.pos(x + width, y, this.zLevel).tex(1, 0).endVertex(); + tessellator.draw(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java new file mode 100644 index 0000000..c8055fb --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java @@ -0,0 +1,64 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.lwjgl.opengl.GL11; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.util.KnockedPlayerData; + +public class KnockedNotificationGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final int KNOCKED_ICON_SIZE = 18; + private static final ResourceLocation KNOCKED_ICON = new ResourceLocation(Tags.MOD_ID, "textures/gui/knocked_icon.png"); + private float totalPartialTicks; + + @Deprecated + @Override + public void render(float partialTicks, ScaledResolution resolution) { + } + + public void renderNotifications(float partialTicks) { + totalPartialTicks += partialTicks; + for (KnockedPlayerData data : KnockedNotificationManager.getKnockedPlayerDatas()) { + Entity entity = client.world.getEntityByID(data.getPlayerId()); + renderKnockedNotification( + entity != null ? entity.getPositionEyes(partialTicks).add(0, 1, 0) : data.getPosition(), + client.getRenderManager().playerViewX, + client.getRenderManager().playerViewY, + client.getRenderManager().options.thirdPersonView == 2 + ); + } + } + + private void renderKnockedNotification(Vec3d position, float pitch, float yaw, boolean isThirdPersonFrontal) { + Vec3d deltaPos = position.subtract(client.getRenderManager().viewerPosX, client.getRenderManager().viewerPosY, client.getRenderManager().viewerPosZ); + + GlStateManager.pushMatrix(); + GlStateManager.translate(deltaPos.x, deltaPos.y, deltaPos.z); + GlStateManager.rotate(-yaw, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate((float)(isThirdPersonFrontal ? -1 : 1) * pitch, 1.0F, 0.0F, 0.0F); + float scale = (float) Math.max(deltaPos.length() / 8.0f, 1.0f) * (0.75f + MathHelper.abs((float) (MathHelper.sin(totalPartialTicks / 10F) / Math.PI))); + GlStateManager.scale(0.05F * scale, 0.05F * scale, 0.05F * scale); + GlStateManager.color(1f, 1f, 1f, 1f); + GlStateManager.disableCull(); + GlStateManager.depthFunc(GL11.GL_ALWAYS); + + client.getTextureManager().bindTexture(KNOCKED_ICON); + this.drawTexture( + -KNOCKED_ICON_SIZE / 2, + -KNOCKED_ICON_SIZE / 2, + KNOCKED_ICON_SIZE, + KNOCKED_ICON_SIZE + ); + + GlStateManager.depthFunc(GL11.GL_LEQUAL); + GlStateManager.enableCull(); + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java new file mode 100644 index 0000000..ad78dbb --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java @@ -0,0 +1,47 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.text.TextFormatting; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +public class ReviveGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + + @Override + public void render(float partialTicks, ScaledResolution resolution) { + EntityPlayer reviving = + client.pointedEntity instanceof EntityPlayer && IKnockdownsPlayerData.get((EntityPlayer) client.pointedEntity).getRevivers().contains(client.player) + ? (EntityPlayer) client.pointedEntity + : client.player; + if (IKnockdownsPlayerData.get(reviving).getReviveTimeLeft() == KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT) { + return; + } + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(reviving); + + FontRenderer font = client.fontRenderer; + + String timerText = String.format("%.1f", data.getReviveTimeLeft() / 20.0f); + float timerX = (resolution.getScaledWidth() - font.getStringWidth(timerText)) * 0.5f; + + data.getRevivers().removeIf(reviver -> reviver.isDead || !reviver.isEntityAlive() || IKnockdownsPlayerData.get(reviver).isKnockedDown()); + int reviverCount = data.getRevivers().size(); + TextFormatting color; + if (reviverCount == 0) { + color = TextFormatting.RED; + } else if (reviverCount == 1) { + color = TextFormatting.WHITE; + } else { + color = TextFormatting.GREEN; + } + + String reviverCountText = "x" + reviverCount; + float reviveCountX = (resolution.getScaledWidth() - font.getStringWidth(reviverCountText)) * 0.5f; + + font.drawStringWithShadow(color + timerText, timerX, resolution.getScaledHeight() * 0.5f + 5, 0xFFFFFF); + font.drawStringWithShadow(color + reviverCountText, reviveCountX, resolution.getScaledHeight() * 0.5f + 14, 0xFFFFFF); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java new file mode 100644 index 0000000..aa82df3 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.client.util; + +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +@SideOnly(Side.CLIENT) +public class Callout { + private final Vec3d position; + private final CalloutType type; + private final long receiveTime; + + public Callout(Vec3d position, CalloutType type, long receiveTime) { + this.position = position; + this.type = type; + this.receiveTime = receiveTime; + } + + public Vec3d getPosition() { + return position; + } + + public CalloutType getType() { + return type; + } + + public long getReceiveTime() { + return receiveTime; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java new file mode 100644 index 0000000..c5089b9 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java @@ -0,0 +1,42 @@ +package ru.octol1ttle.knockdowns.client.util; + +import javax.annotation.Nullable; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.MovingSound; +import net.minecraft.entity.Entity; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class DirectionalCallSound extends MovingSound { + private final @Nullable Entity entity; + private final Vec3d position; + private int time; + + public DirectionalCallSound(SoundEvent event, @Nullable Entity entity, Vec3d position) { + super(event, SoundCategory.PLAYERS); + this.entity = entity; + this.position = position; + } + + @Override + public void update() { + this.time++; + if (this.time > 40 || this.entity != null && this.entity.isDead) { + this.donePlaying = true; + return; + } + + Minecraft client = Minecraft.getMinecraft(); + Vec3d calloutPos = this.entity != null ? this.entity.getPositionVector() : this.position; + Vec3d directionVec = calloutPos.subtract(client.player.getPositionVector()).normalize(); + Vec3d finalPos = client.player.getPositionVector().add(directionVec); + + this.xPosF = (float) finalPos.x; + this.yPosF = (float) finalPos.y; + this.zPosF = (float) finalPos.z; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java new file mode 100644 index 0000000..8cebdde --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java @@ -0,0 +1,27 @@ +package ru.octol1ttle.knockdowns.client.util; + +import net.minecraft.util.math.Vec3d; + +public class KnockedPlayerData { + private final int playerId; + private final Vec3d position; + private final long receiveTime; + + public KnockedPlayerData(int playerId, Vec3d position, long receiveTime) { + this.playerId = playerId; + this.position = position; + this.receiveTime = receiveTime; + } + + public int getPlayerId() { + return playerId; + } + + public Vec3d getPosition() { + return position; + } + + public long getReceiveTime() { + return receiveTime; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java b/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java new file mode 100644 index 0000000..d1db4ea --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java @@ -0,0 +1,21 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public interface IClientProxy { + void onFMLInit(FMLInitializationEvent event); + + T handleMessage(IMessage message); + + class Dummy implements IClientProxy { + @Override + public void onFMLInit(FMLInitializationEvent event) { + } + + @Override + public T handleMessage(IMessage message) { + return null; + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java new file mode 100644 index 0000000..30ad689 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java @@ -0,0 +1,259 @@ +package ru.octol1ttle.knockdowns.common; + +import java.util.List; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.MobEffects; +import net.minecraft.potion.PotionEffect; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.data.KnockdownsCapability; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_HURT_PERIOD; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_INVULNERABILITY_TICKS; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_TENACITY; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.allPlayersKnocked; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.resetKnockedState; + +@Mod.EventBusSubscriber(modid = Tags.MOD_ID) +public class KnockdownsCommonEventListener { + public static void onFMLInit(FMLInitializationEvent event) { + KnockdownsMod.LOGGER.info("Registering network packets"); + KnockdownsNetwork.registerPackets(); + KnockdownsMod.LOGGER.info("Registering capability"); + KnockdownsCapability.register(); + } + + @SubscribeEvent + public static void onSoundsRegister(RegistryEvent.Register event) { + event.getRegistry().register(KnockdownsSoundEvents.CALLOUT); + } + + @SubscribeEvent + public static void onCapabilitiesAttach(AttachCapabilitiesEvent event) { + if (event.getObject() instanceof EntityPlayer) { + event.addCapability(KnockdownsCapability.ID, new KnockdownsCapability()); + } + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + MinecraftServer server = event.player.getServer(); + if (event.phase == TickEvent.Phase.START || server == null) { + return; + } + EntityPlayerMP knocked = (EntityPlayerMP) event.player; + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(knocked); + if (!data.isKnockedDown()) { + return; + } + + if (allPlayersKnocked(server, knocked)) { + knocked.attackEntityFrom(DamageSource.GENERIC, knocked.getMaxHealth()); + return; + } + + List revivers = data.getRevivers(); + revivers.removeIf(reviver -> reviver.isDead || !reviver.isEntityAlive() || IKnockdownsPlayerData.get(reviver).isKnockedDown()); + if (!revivers.isEmpty()) { + data.setReviveTimeLeft(data.getReviveTimeLeft() - revivers.size()); + KnockdownsNetwork.sendToMultiple( + new SynchronizePlayerDataS2CPacket.ReviveTimeLeft(knocked.getEntityId(), data.getReviveTimeLeft()), + revivers, + knocked + ); + + if (data.getReviveTimeLeft() <= 0) { + resetKnockedState(knocked, data); + + knocked.setEntityInvulnerable(false); + knocked.setHealth(knocked.getMaxHealth() * 0.3f); + knocked.setAbsorptionAmount(0.0f); + } + return; + } + + int oldReviveTimeLeft = data.getReviveTimeLeft(); + data.setReviveTimeLeft(Math.min(INITIAL_REVIVE_TIME_LEFT, oldReviveTimeLeft + 1)); + if (data.getReviveTimeLeft() != oldReviveTimeLeft) { + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.ReviveTimeLeft(knocked.getEntityId(), data.getReviveTimeLeft()), + knocked + ); + } + + data.setTicksKnocked(data.getTicksKnocked() + 1); + + int period = MathHelper.floor(KNOCKED_HURT_PERIOD * 20); + if (data.getTicksKnocked() >= KNOCKED_INVULNERABILITY_TICKS && data.getTicksKnocked() % period == 0) { + knocked.setEntityInvulnerable(false); + knocked.attackEntityFrom(DamageSource.GENERIC, knocked.getMaxHealth() / (KNOCKED_TENACITY / KNOCKED_HURT_PERIOD)); + } + } + + @SubscribeEvent + public static void onPlayerDeath(LivingDeathEvent event) { + if (!(event.getEntityLiving() instanceof EntityPlayerMP)) { + return; + } + + EntityPlayerMP player = (EntityPlayerMP) event.getEntityLiving(); + IKnockdownsPlayerData data = player.getCapability(KnockdownsCapability.CAPABILITY, null); + if (data == null) { + return; + } + + if (data.isKnockedDown() || allPlayersKnocked(player.getServer(), player)) { + data.getRevivers().clear(); + return; + } + + player.clearActivePotions(); + player.setEntityInvulnerable(true); + player.setHealth(1.0f); + player.setAbsorptionAmount(player.getMaxHealth() - 1.0f); + player.extinguish(); + player.setAir(300); + player.clearElytraFlying(); + + player.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 6000, 3)); + + Entity trueSource = event.getSource().getTrueSource(); + if (trueSource instanceof EntityLiving) { + ((EntityLiving) trueSource).setAttackTarget(null); + } + + data.setKnockedDown(true); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.setTicksKnocked(0); + + KnockdownsNetwork.sendToAll(new PlayerKnockedDownS2CPacket(player.getEntityId(), player.dimension, player.getPositionEyes(1).add(0, 1, 0))); + + TextComponentTranslation deathMessage = (TextComponentTranslation) player.getCombatTracker().getDeathMessage(); + + String knockdownKey = deathMessage.getKey().replace("death.", "knockdown."); + TextComponentTranslation knockdownTranslation = new TextComponentTranslation(knockdownKey, deathMessage.getFormatArgs()); + player.getServer().getPlayerList().sendMessage( + !knockdownTranslation.getUnformattedComponentText().equals(knockdownKey) ? knockdownTranslation : deathMessage, + true + ); + + event.setCanceled(true); + } + + @SubscribeEvent + public static void onMobTarget(LivingSetAttackTargetEvent event) { + if (event.getTarget() instanceof EntityPlayer && IKnockdownsPlayerData.get((EntityPlayer) event.getTarget()).isKnockedDown()) { + ((EntityLiving)event.getEntityLiving()).setAttackTarget(null); + } + } + + @SubscribeEvent + public static void onPlayerStartTracking(PlayerEvent.StartTracking event) { + if (event.getTarget() instanceof EntityPlayerMP) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get((EntityPlayer) event.getTarget()); + if (data.isKnockedDown()) { + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.KnockedDown( + event.getTarget().getEntityId(), + data.isKnockedDown() + ), + (EntityPlayerMP) event.getEntityPlayer() + ); + } + } + } + + @SubscribeEvent + public static void onKnockedAttack(AttackEntityEvent event) { + if (IKnockdownsPlayerData.get(event.getEntityPlayer()).isKnockedDown()) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onKnockedInteraction(PlayerInteractEvent event) { + if (IKnockdownsPlayerData.get(event.getEntityPlayer()).isKnockedDown()) { + if (!(event instanceof PlayerInteractEvent.RightClickBlock) && event.isCancelable()) { + event.setCanceled(true); + event.setCancellationResult(EnumActionResult.FAIL); + } + } else if (event instanceof PlayerInteractEvent.EntityInteract) { + onPlayerInteraction((PlayerInteractEvent.EntityInteract) event); + } + } + + public static void onPlayerInteraction(PlayerInteractEvent.EntityInteract event) { + if (event.getTarget() instanceof EntityPlayerMP) { + EntityPlayerMP knocked = (EntityPlayerMP) event.getTarget(); + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(knocked); + if (data.isKnockedDown() && !data.getRevivers().contains(event.getEntityPlayer())) { + for (EntityPlayer reviver : data.getRevivers()) { + KnockdownsNetwork.sendToPlayer( + new SynchronizeReviversS2CPacket.Add(event.getTarget().getEntityId(), reviver.getEntityId()), + (EntityPlayerMP) event.getEntityPlayer() + ); + } + + data.getRevivers().add(event.getEntityPlayer()); + KnockdownsNetwork.sendToMultiple( + new SynchronizeReviversS2CPacket.Add(event.getTarget().getEntityId(), event.getEntityPlayer().getEntityId()), + data.getRevivers(), + knocked + ); + } + } + } + + @SubscribeEvent + public static void onPlayerJoin(PlayerLoggedInEvent event) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(event.player); + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.Full(event.player.getEntityId(), data.isKnockedDown(), data.getReviveTimeLeft()), + (EntityPlayerMP) event.player + ); + } + + @SubscribeEvent + public static void onPlayerLeave(PlayerLoggedOutEvent event) { + for (EntityPlayer knocked : event.player.world.playerEntities) { + List revivers = IKnockdownsPlayerData.get(knocked).getRevivers(); + if (revivers.contains(event.player)) { + revivers.remove(event.player); + KnockdownsNetwork.sendToMultiple( + new SynchronizeReviversS2CPacket.Remove(knocked.getEntityId(), event.player.getEntityId()), + revivers, + (EntityPlayerMP) knocked + ); + break; + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java new file mode 100644 index 0000000..77e881b --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java @@ -0,0 +1,43 @@ +package ru.octol1ttle.knockdowns.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import zone.rong.mixinbooter.IEarlyMixinLoader; + +@IFMLLoadingPlugin.MCVersion("1.12.2") +public class KnockdownsFMLLoadingPlugin implements IFMLLoadingPlugin, IEarlyMixinLoader { + @Override + public String[] getASMTransformerClass() { + return null; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Nullable + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + } + + @Override + public String getAccessTransformerClass() { + return null; + } + + @Override + public List getMixinConfigs() { + ArrayList list = new ArrayList<>(); + list.add("mixins.knockdowns.json"); + return list; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java new file mode 100644 index 0000000..49e365c --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java @@ -0,0 +1,22 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import ru.octol1ttle.knockdowns.Tags; + +@Mod(modid = Tags.MOD_ID, name = Tags.MOD_NAME, version = Tags.VERSION) +public class KnockdownsMod { + public static final Logger LOGGER = LogManager.getLogger(Tags.MOD_NAME); + @SidedProxy(clientSide = "ru.octol1ttle.knockdowns.client.ClientProxy", serverSide = "ru.octol1ttle.knockdowns.common.IClientProxy$Dummy") + public static IClientProxy clientProxy; + + @Mod.EventHandler + public void onFMLInit(FMLInitializationEvent event) { + LOGGER.info("Initializing"); + clientProxy.onFMLInit(event); + KnockdownsCommonEventListener.onFMLInit(event); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java new file mode 100644 index 0000000..4b577f2 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java @@ -0,0 +1,43 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.MobEffects; +import net.minecraft.server.MinecraftServer; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; + +public class KnockdownsUtils { + public static final int INITIAL_REVIVE_TIME_LEFT = 200; + public static final float KNOCKED_INVULNERABILITY_TICKS = 3.0f * 20.0f; + public static final float KNOCKED_HURT_PERIOD = 1.2f; + public static final float KNOCKED_TENACITY = 60.0f; + + public static boolean allPlayersKnocked(MinecraftServer server, EntityPlayer except) { + for (EntityPlayer player : server.getPlayerList().getPlayers()) { + if (player.equals(except)) { + continue; + } + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(player); + if (player.isEntityAlive() && !data.isKnockedDown()) { + return false; + } + } + return true; + } + + public static void resetKnockedState(EntityPlayerMP player, IKnockdownsPlayerData data) { + player.removePotionEffect(MobEffects.SLOWNESS); + data.setKnockedDown(false); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.setTicksKnocked(0); + + KnockdownsNetwork.sendToTrackingAndSelf( + new SynchronizePlayerDataS2CPacket.KnockedDown(player.getEntityId(), data.isKnockedDown()), + player + ); + + data.getRevivers().clear(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java b/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java new file mode 100644 index 0000000..56ecb03 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java @@ -0,0 +1,35 @@ +package ru.octol1ttle.knockdowns.common.communication; + +public enum CalloutType { + DANGER((byte) 0, "knockdowns.callout.danger"), + BOOYAH((byte) 1, "knockdowns.callout.booyah"), + THIS_WAY((byte) 2, "knockdowns.callout.this_way"), + OUCH((byte) 3, "knockdowns.callout.ouch"), + HELP((byte) 4, "knockdowns.callout.help"); + + private final byte id; + private final String textKey; + + CalloutType(byte id, String textKey) { + this.id = id; + this.textKey = textKey; + } + + public byte getId() { + return id; + } + + public String getTextKey() { + return textKey; + } + + public static CalloutType byId(byte id) { + for (CalloutType type : CalloutType.values()) { + if (id == type.getId()) { + return type; + } + } + + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java new file mode 100644 index 0000000..820c8a0 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java @@ -0,0 +1,24 @@ +package ru.octol1ttle.knockdowns.common.data; + +import java.util.Objects; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.INBTSerializable; +import ru.octol1ttle.knockdowns.common.util.UniqueOnlyList; + +public interface IKnockdownsPlayerData extends INBTSerializable { + boolean isKnockedDown(); + void setKnockedDown(boolean knockedDown); + + int getReviveTimeLeft(); + void setReviveTimeLeft(int reviveTimeLeft); + + int getTicksKnocked(); + void setTicksKnocked(int ticksKnocked); + + UniqueOnlyList getRevivers(); + + static IKnockdownsPlayerData get(EntityPlayer player) { + return Objects.requireNonNull(player.getCapability(KnockdownsCapability.CAPABILITY, null)); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java new file mode 100644 index 0000000..d25ad94 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java @@ -0,0 +1,65 @@ +package ru.octol1ttle.knockdowns.common.data; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.LazyLoadBase; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import ru.octol1ttle.knockdowns.Tags; + +public class KnockdownsCapability implements ICapabilitySerializable { + @CapabilityInject(IKnockdownsPlayerData.class) + public static Capability CAPABILITY; + public static final ResourceLocation ID = new ResourceLocation(Tags.MOD_ID, "data"); + + private KnockdownsPlayerData playerData = null; + private final LazyLoadBase lazy = new LazyLoadBase() { + @Override + protected KnockdownsPlayerData load() { + return playerData == null ? (playerData = new KnockdownsPlayerData()) : playerData; + } + }; + + public static void register() { + CapabilityManager.INSTANCE.register(IKnockdownsPlayerData.class, new Capability.IStorage() { + @Nullable + @Override + public NBTBase writeNBT(Capability capability, IKnockdownsPlayerData instance, EnumFacing side) { + return instance.serializeNBT(); + } + + @Override + public void readNBT(Capability capability, IKnockdownsPlayerData instance, EnumFacing side, NBTBase nbt) { + instance.deserializeNBT((NBTTagCompound) nbt); + } + }, KnockdownsPlayerData::new); + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CAPABILITY ? (T) lazy.getValue() : null; + } + + @Override + public NBTTagCompound serializeNBT() { + return lazy.getValue().serializeNBT(); + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + lazy.getValue().deserializeNBT(nbt); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java new file mode 100644 index 0000000..cbff961 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java @@ -0,0 +1,67 @@ +package ru.octol1ttle.knockdowns.common.data; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.util.UniqueOnlyList; + +public class KnockdownsPlayerData implements IKnockdownsPlayerData { + private static final String KEY_KNOCKED_DOWN = "KnockedDown"; + private static final String KEY_REVIVE_TIME_LEFT = "ReviveTimeLeft"; + private static final String KEY_TICKS_KNOCKED = "TicksKnocked"; + private boolean knockedDown = false; + private int reviveTimeLeft = KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; + private int ticksKnocked = 0; + private final UniqueOnlyList revivers = new UniqueOnlyList<>(); + + @Override + public boolean isKnockedDown() { + return this.knockedDown; + } + + @Override + public void setKnockedDown(boolean knockedDown) { + this.knockedDown = knockedDown; + } + + @Override + public int getReviveTimeLeft() { + return this.reviveTimeLeft; + } + + @Override + public void setReviveTimeLeft(int reviveTimeLeft) { + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public int getTicksKnocked() { + return this.ticksKnocked; + } + + @Override + public void setTicksKnocked(int ticksKnocked) { + this.ticksKnocked = ticksKnocked; + } + + @Override + public UniqueOnlyList getRevivers() { + return revivers; + } + + @Override + public NBTTagCompound serializeNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setBoolean(KEY_KNOCKED_DOWN, this.knockedDown); + nbt.setInteger(KEY_REVIVE_TIME_LEFT, this.reviveTimeLeft); + nbt.setInteger(KEY_TICKS_KNOCKED, this.ticksKnocked); + return nbt; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + this.knockedDown = nbt.getBoolean(KEY_KNOCKED_DOWN); + this.reviveTimeLeft = nbt.getInteger(KEY_REVIVE_TIME_LEFT); + this.ticksKnocked = nbt.getInteger(KEY_TICKS_KNOCKED); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java new file mode 100644 index 0000000..8bcea94 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.common.mixins; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +@SuppressWarnings("ConstantValue") +@Mixin(EntityLivingBase.class) +public abstract class EntityLivingBaseMixin extends Entity { + public EntityLivingBaseMixin(World worldIn) { + super(worldIn); + } + + @Inject(method = "checkTotemDeathProtection", at = @At("RETURN")) + public void onTotemActivation(CallbackInfoReturnable cir) { + if (cir.getReturnValue() && ((Object) this) instanceof EntityPlayerMP) { + EntityPlayerMP player = (EntityPlayerMP) (Object) this; + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(player); + if (data.isKnockedDown()) { + KnockdownsUtils.resetKnockedState(player, data); + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java new file mode 100644 index 0000000..e1684ac --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java @@ -0,0 +1,22 @@ +package ru.octol1ttle.knockdowns.common.mixins; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.entity.player.EntityPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +@SuppressWarnings("ConstantValue") +@Mixin(EntityPlayer.class) +public class EntityPlayerMixin { + @ModifyReturnValue(method = "shouldHeal", at = @At("RETURN")) + private boolean dontHealIfKnockedDown(boolean original) { + if (((Object) this) instanceof EntityPlayer) { + EntityPlayer player = (EntityPlayer) (Object) this; + if (IKnockdownsPlayerData.get(player).isKnockedDown()) { + return false; + } + } + return original; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java new file mode 100644 index 0000000..6e5fa60 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java @@ -0,0 +1,73 @@ +package ru.octol1ttle.knockdowns.common.network; + +import java.util.List; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.common.KnockdownsMod; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; + +public class KnockdownsNetwork { + private static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel(Tags.MOD_ID); + private static int packetId = 0; + + public static void registerPackets() { + INSTANCE.registerMessage(KnockdownsServerPacketHandler.Callout.class, PlayerCalloutC2SPacket.class, packetId++, Side.SERVER); + INSTANCE.registerMessage(KnockdownsServerPacketHandler.CancelRevive.class, CancelReviveC2SPacket.class, packetId++, Side.SERVER); + + IMessageHandler clientProxyHandler = (message, ctx) -> KnockdownsMod.clientProxy.handleMessage(message); + INSTANCE.registerMessage(clientProxyHandler, PlayerCalloutS2CPacket.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, PlayerKnockedDownS2CPacket.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, SynchronizePlayerDataS2CPacket.KnockedDown.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, SynchronizePlayerDataS2CPacket.ReviveTimeLeft.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, SynchronizePlayerDataS2CPacket.Full.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, SynchronizeReviversS2CPacket.Add.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(clientProxyHandler, SynchronizeReviversS2CPacket.Remove.class, packetId++, Side.CLIENT); + } + + @SideOnly(Side.CLIENT) + public static void sendToServer(IMessage message) { + INSTANCE.sendToServer(message); + } + + public static void sendToAll(IMessage message) { + INSTANCE.sendToAll(message); + } + + public static void sendToDimension(IMessage message, MessageContext context) { + INSTANCE.sendToDimension(message, context.getServerHandler().player.dimension); + } + + public static void sendToTrackingAndSelf(IMessage message, EntityPlayerMP player) { + sendToPlayer(message, player); + sendToTracking(message, player); + } + + public static void sendToMultiple(IMessage message, List players, EntityPlayerMP player) { + sendToPlayer(message, player); + for (EntityPlayer listed : players) { + sendToPlayer(message, (EntityPlayerMP) listed); + } + } + + public static void sendToTracking(IMessage message, Entity entity) { + INSTANCE.sendToAllTracking(message, entity); + } + + public static void sendToPlayer(IMessage message, EntityPlayerMP player) { + INSTANCE.sendTo(message, player); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java new file mode 100644 index 0000000..d0beb64 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java @@ -0,0 +1,51 @@ +package ru.octol1ttle.knockdowns.common.network; + +import java.util.List; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; + +public class KnockdownsServerPacketHandler { + public static class Callout implements IMessageHandler { + @Override + public IMessage onMessage(PlayerCalloutC2SPacket message, MessageContext ctx) { + KnockdownsNetwork.sendToDimension( + new PlayerCalloutS2CPacket( + ctx.getServerHandler().player.getEntityId(), + ctx.getServerHandler().player.getPositionEyes(1).add(0, 1, 0), + message.type + ), + ctx + ); + return null; + } + } + + public static class CancelRevive implements IMessageHandler { + @Override + public IMessage onMessage(CancelReviveC2SPacket message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + for (EntityPlayer knocked : player.world.playerEntities) { + List revivers = IKnockdownsPlayerData.get(knocked).getRevivers(); + if (revivers.contains(player)) { + revivers.remove(player); + KnockdownsNetwork.sendToMultiple( + new SynchronizeReviversS2CPacket.Remove(knocked.getEntityId(), player.getEntityId()), + revivers, + (EntityPlayerMP) knocked + ); + break; + } + } + + return null; + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java new file mode 100644 index 0000000..dce5e9a --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java @@ -0,0 +1,17 @@ +package ru.octol1ttle.knockdowns.common.network.packets.c2s; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class CancelReviveC2SPacket implements IMessage { + public CancelReviveC2SPacket() { + } + + @Override + public void toBytes(ByteBuf buf) { + } + + @Override + public void fromBytes(ByteBuf buf) { + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java new file mode 100644 index 0000000..580d5fa --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java @@ -0,0 +1,25 @@ +package ru.octol1ttle.knockdowns.common.network.packets.c2s; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +public class PlayerCalloutC2SPacket implements IMessage { + public PlayerCalloutC2SPacket() { + } + + public CalloutType type; + public PlayerCalloutC2SPacket(CalloutType type) { + this.type = type; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeByte(this.type.getId()); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.type = CalloutType.byId(buf.readByte()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java new file mode 100644 index 0000000..34752dd --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java @@ -0,0 +1,37 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +public class PlayerCalloutS2CPacket implements IMessage { + public PlayerCalloutS2CPacket() { + } + + public int playerId; + public Vec3d position; + public CalloutType type; + + public PlayerCalloutS2CPacket(int playerId, Vec3d position, CalloutType type) { + this.playerId = playerId; + this.position = position; + this.type = type; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + buf.writeByte(this.type.getId()); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.position = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + this.type = CalloutType.byId(buf.readByte()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java new file mode 100644 index 0000000..017bb4a --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java @@ -0,0 +1,36 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class PlayerKnockedDownS2CPacket implements IMessage { + public PlayerKnockedDownS2CPacket() { + } + + public int playerId; + public int dimensionId; + public Vec3d position; + + public PlayerKnockedDownS2CPacket(int playerId, int dimensionId, Vec3d position) { + this.playerId = playerId; + this.dimensionId = dimensionId; + this.position = position; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeInt(this.dimensionId); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.dimensionId = buf.readInt(); + this.position = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java new file mode 100644 index 0000000..c5846ab --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java @@ -0,0 +1,85 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class SynchronizePlayerDataS2CPacket { + public static class KnockedDown implements IMessage { + public KnockedDown() { + } + + public int playerId; + public boolean knockedDown; + + public KnockedDown(int playerId, boolean knockedDown) { + this.playerId = playerId; + this.knockedDown = knockedDown; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeBoolean(this.knockedDown); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.knockedDown = buf.readBoolean(); + } + } + + public static class ReviveTimeLeft implements IMessage { + public ReviveTimeLeft() { + } + + public int playerId; + public int reviveTimeLeft; + + public ReviveTimeLeft(int playerId, int reviveTimeLeft) { + this.playerId = playerId; + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeInt(this.reviveTimeLeft); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.reviveTimeLeft = buf.readInt(); + } + } + + public static class Full implements IMessage { + public Full() { + } + + public int playerId; + public boolean knockedDown; + public int reviveTimeLeft; + + public Full(int playerId, boolean knockedDown, int reviveTimeLeft) { + this.playerId = playerId; + this.knockedDown = knockedDown; + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeBoolean(this.knockedDown); + buf.writeInt(this.reviveTimeLeft); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.knockedDown = buf.readBoolean(); + this.reviveTimeLeft = buf.readInt(); + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java new file mode 100644 index 0000000..480c701 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java @@ -0,0 +1,56 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class SynchronizeReviversS2CPacket { + public static class Add implements IMessage { + public Add() { + } + + public int knockedId; + public int reviverId; + + public Add(int knockedId, int reviverId) { + this.knockedId = knockedId; + this.reviverId = reviverId; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.knockedId); + buf.writeInt(this.reviverId); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.knockedId = buf.readInt(); + this.reviverId = buf.readInt(); + } + } + + public static class Remove implements IMessage { + public Remove() { + } + + public int knockedId; + public int reviverId; + + public Remove(int knockedId, int reviverId) { + this.knockedId = knockedId; + this.reviverId = reviverId; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.knockedId); + buf.writeInt(this.reviverId); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.knockedId = buf.readInt(); + this.reviverId = buf.readInt(); + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java b/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java new file mode 100644 index 0000000..c90437d --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java @@ -0,0 +1,17 @@ +package ru.octol1ttle.knockdowns.common.registry; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import ru.octol1ttle.knockdowns.Tags; + +public class KnockdownsSoundEvents { + public static final SoundEvent CALLOUT; + public static final SoundEvent KNOCKED_DOWN; + static { + ResourceLocation callout = new ResourceLocation(Tags.MOD_ID, "callout"); + CALLOUT = new SoundEvent(callout).setRegistryName(callout); + + ResourceLocation knockedDown = new ResourceLocation(Tags.MOD_ID, "knocked_down"); + KNOCKED_DOWN = new SoundEvent(knockedDown).setRegistryName(knockedDown); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/util/UniqueOnlyList.java b/src/main/java/ru/octol1ttle/knockdowns/common/util/UniqueOnlyList.java new file mode 100644 index 0000000..de5fcb4 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/util/UniqueOnlyList.java @@ -0,0 +1,23 @@ +package ru.octol1ttle.knockdowns.common.util; + +import java.util.ArrayList; + +public class UniqueOnlyList extends ArrayList { + @Override + public void add(int index, T element) { + throw new IllegalStateException(); + } + + @Override + public boolean add(T t) { + if (this.contains(t)) { + return false; + } + return super.add(t); + } + + @Override + public T set(int index, T element) { + throw new IllegalStateException(); + } +} diff --git a/src/main/resources/assets/knockdowns/lang/en_us.lang b/src/main/resources/assets/knockdowns/lang/en_us.lang new file mode 100644 index 0000000..a9042b8 --- /dev/null +++ b/src/main/resources/assets/knockdowns/lang/en_us.lang @@ -0,0 +1,65 @@ +knockdowns.key.category=Knockdowns +knockdowns.key.callout.danger=Call out "Danger!" +knockdowns.key.callout.booyah=Call out "Booyah!" +knockdowns.key.callout.this_way_help=Call out "This way!" ("Help!" when knocked down) +knockdowns.key.callout.ouch=Call out "Ouch..." + +knockdowns.callout.danger=Danger! +knockdowns.callout.booyah=Booyah! +knockdowns.callout.this_way=This way! +knockdowns.callout.ouch=Ouch... +knockdowns.callout.help=Help! + +knockdowns.subtitles.callout=Player calls out +knockdowns.subtitles.knocked_down=Player knocked down + +knockdown.fell.accident.ladder=%1$s was knocked down by falling off a ladder +knockdown.fell.accident.vines=%1$s was knocked down by falling off some vines +knockdown.fell.accident.water=%1$s was knocked down by falling out of the water +knockdown.fell.accident.generic=%1$s was knocked down by a fall +knockdown.fell.killer=%1$s was doomed to get knocked down +knockdown.fell.assist=%1$s was doomed to get knocked down by %2$s +knockdown.fell.assist.item=%1$s was doomed to get knocked down by %2$s using %3$s +knockdown.fell.finish=%1$s fell too far and was knocked down by %2$s +knockdown.fell.finish.item=%1$s fell too far and was knocked down by %2$s using %3$s + +knockdown.attack.lightningBolt=%1$s was knocked down by lightning +knockdown.attack.inFire=%1$s was knocked down by the fire below +knockdown.attack.inFire.player=%1$s was knocked down by the fire below whilst fighting %2$s +knockdown.attack.onFire=%1$s was knocked down by fire +knockdown.attack.onFire.player=%1$s was knocked down by fire whilst fighting %2$s +knockdown.attack.lava=%1$s was knocked down by lava +knockdown.attack.lava.player=%1$s was knocked down by lava to escape %2$s +knockdown.attack.hotFloor=%1$s was knocked down by the floor +knockdown.attack.hotFloor.player=%1$s was knocked down by the floor due to %2$s +knockdown.attack.inWall=%1$s was knocked down by a wall +knockdown.attack.cramming=%1$s was knocked down by social anxiety +knockdown.attack.drown=%1$s was knocked down by lack of air +knockdown.attack.drown.player=%1$s was knocked down by lack of air whilst trying to escape %2$s +knockdown.attack.starve=%1$s was knocked down by hunger +knockdown.attack.cactus=%1$s was knocked down by a cactus +knockdown.attack.cactus.player=%1$s was knocked down by a cactus whilst trying to escape %2$s +knockdown.attack.generic=%1$s was knocked down +knockdown.attack.explosion=%1$s was knocked down by an explosion +knockdown.attack.explosion.player=%1$s was knocked down by an explosion due to %2$s +knockdown.attack.magic=%1$s was knocked down by magic +knockdown.attack.wither=%1$s was knocked down by withering +knockdown.attack.anvil=%1$s was knocked down by a falling anvil +knockdown.attack.fallingBlock=%1$s was knocked down by a falling block +knockdown.attack.mob=%1$s was knocked down by %2$s +knockdown.attack.player=%1$s was knocked down by %2$s +knockdown.attack.player.item=%1$s was knocked down by %2$s using %3$s +knockdown.attack.arrow=%1$s was knocked down by an arrow shot by %2$s +knockdown.attack.arrow.item=%1$s was knocked down by an arrow shot by %2$s using %3$s +knockdown.attack.fireball=%1$s was knocked down by a fireball shot by %2$s +knockdown.attack.fireball.item=%1$s was knocked down by a fireball shot by %2$s using %3$s +knockdown.attack.thrown=%1$s was knocked down after being pummeled by %2$s +knockdown.attack.thrown.item=%1$s was knocked down after being pummeled by %2$s using %3$s +knockdown.attack.indirectMagic=%1$s was knocked down by %2$s using magic +knockdown.attack.indirectMagic.item=%1$s was knocked down by %2$s using %3$s +knockdown.attack.thorns=%1$s was knocked down trying to hurt %2$s +knockdown.attack.fall=%1$s was knocked down by the ground below +knockdown.attack.outOfWorld=%1$s was knocked down by the void +knockdown.attack.dragonBreath=%1$s was knocked down by dragon breath +knockdown.attack.flyIntoWall=%1$s was knocked down by physics +knockdown.attack.fireworks=%1$s was knocked down by a firework diff --git a/src/main/resources/assets/knockdowns/lang/ru_ru.lang b/src/main/resources/assets/knockdowns/lang/ru_ru.lang new file mode 100644 index 0000000..a45d2e2 --- /dev/null +++ b/src/main/resources/assets/knockdowns/lang/ru_ru.lang @@ -0,0 +1,64 @@ +knockdowns.key.category=Knockdowns +knockdowns.key.callout.danger="Опасность!" +knockdowns.key.callout.booyah="Йо-хо!" +knockdowns.key.callout.this_way_help="Сюда!" ("SOS!" когда тяжело ранен) +knockdowns.key.callout.ouch="Непруха!" + +knockdowns.callout.danger=Опасность! +knockdowns.callout.booyah=Йо-хо! +knockdowns.callout.this_way=Сюда! +knockdowns.callout.ouch=Непруха! +knockdowns.callout.help=SOS! + +knockdowns.subtitles.callout=Игрок зовёт союзников +knockdowns.subtitles.knocked_down=Игрок тяжело ранен + +knockdown.attack.anvil=%1$s тяжело ранен упавшей наковальней +knockdown.attack.arrow=%1$s тяжело ранен стрелой %2$s +knockdown.attack.arrow.item=%1$s тяжело ранен стрелой %2$s с помощью %3$s +knockdown.attack.cactus=%1$s исколот до тяжелого ранения +knockdown.attack.cactus.player=%1$s тяжело ранен кактусом, спасаясь от %2$s +knockdown.attack.cramming=%1$s расплющен до тяжелого ранения +knockdown.attack.dragonBreath=%1$s тяжело ранен в драконьем дыхании +knockdown.attack.drown=%1$s тяжело ранен от нехватки воздуха +knockdown.attack.drown.player=%1$s тяжело ранен от нехватки воздуха, спасаясь от %2$s +knockdown.attack.explosion=%1$s тяжело ранен взрывом +knockdown.attack.explosion.player=%1$s был тяжело ранен взрывом %2$s +knockdown.attack.fall=%1$s разбился до тяжелого ранения +knockdown.attack.fallingBlock=%1$s тяжело ранен упавшим блоком +knockdown.attack.fireball=%1$s тяжело ранен файерболом %2$s +knockdown.attack.fireball.item=%1$s тяжело ранен файерболом %2$s с помощью %3$s +knockdown.attack.fireworks=%1$s с треском тяжело ранен +knockdown.attack.flyIntoWall=%1$s преобразовал кинетическую энергию в тяжелое ранение +knockdown.attack.generic=%1$s тяжело ранен +knockdown.attack.hotFloor=%1$s тяжело ранен, обнаружив под ногами лаву +knockdown.attack.hotFloor.player=%1$s зашёл в опасную зону тяжелого ранения из-за %2$s +knockdown.attack.inFire=%1$s сгорел до тяжелого ранения +knockdown.attack.inFire.player=%1$s тяжело ранен в огне, борясь с %2$s +knockdown.attack.inWall=%1$s погребён до тяжелого ранения +knockdown.attack.indirectMagic=%1$s был тяжело ранен %2$s с помощью магии +knockdown.attack.indirectMagic.item=%1$s был тяжело ранен %2$s с помощью %3$s +knockdown.attack.lava=%1$s решил получить тяжелое ранение в лаве +knockdown.attack.lava.player=%1$s получил тяжелое ранение от лавы, убегая от %2$s +knockdown.attack.lightningBolt=%1$s был тяжело ранен поражением молнией +knockdown.attack.magic=%1$s был тяжело ранен магией +knockdown.attack.mob=%1$s был тяжело ранен %2$s +knockdown.attack.onFire=%1$s сгорел до тяжелого ранения +knockdown.attack.onFire.player=%1$s был сожжён до тяжелого ранения, пока боролся с %2$s +knockdown.attack.outOfWorld=%1$s тяжело ранен отсутствием земли под собой +knockdown.attack.player=%1$s был тяжело ранен %2$s +knockdown.attack.player.item=%1$s был тяжело ранен %2$s с помощью %3$s +knockdown.attack.starve=%1$s тяжело ранен от голода +knockdown.attack.thorns=%1$s был тяжело ранен, пытаясь навредить %2$s +knockdown.attack.thrown=%1$s был избит до тяжелого ранения %2$s +knockdown.attack.thrown.item=%1$s был избит до тяжелого ранения %2$s с помощью %3$s +knockdown.attack.wither=%1$s тяжело ранен иссушением +knockdown.fell.accident.generic=%1$s разбился до тяжелого ранения +knockdown.fell.accident.ladder=%1$s свалился с лестницы и был тяжело ранен +knockdown.fell.accident.vines=%1$s сорвался с лианы и был тяжело ранен +knockdown.fell.accident.water=%1$s выпал из воды и был тяжело ранен +knockdown.fell.assist=%1$s свалился и был тяжело ранен благодаря %2$s +knockdown.fell.assist.item=%1$s был обречён на тяжелое ранение %2$s с помощью %3$s +knockdown.fell.finish=%1$s упал с высоты и был тяжело ранен %2$s +knockdown.fell.finish.item=%1$s упал с высоты и был тяжело ранен %2$s с помощью %3$s +knockdown.fell.killer=%1$s был обречён на тяжелое ранение diff --git a/src/main/resources/assets/knockdowns/sounds.json b/src/main/resources/assets/knockdowns/sounds.json new file mode 100755 index 0000000..fabd199 --- /dev/null +++ b/src/main/resources/assets/knockdowns/sounds.json @@ -0,0 +1,18 @@ +{ + "callout": { + "subtitle": "knockdowns.subtitles.callout", + "sounds": [ + { + "name": "knockdowns:callout" + } + ] + }, + "knocked_down": { + "subtitle": "knockdowns.subtitles.knocked_down", + "sounds": [ + { + "name": "knockdowns:knocked_down" + } + ] + } +} diff --git a/src/main/resources/assets/knockdowns/sounds/callout.ogg b/src/main/resources/assets/knockdowns/sounds/callout.ogg new file mode 100644 index 0000000..93c71da Binary files /dev/null and b/src/main/resources/assets/knockdowns/sounds/callout.ogg differ diff --git a/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg b/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg new file mode 100755 index 0000000..742eb84 Binary files /dev/null and b/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg differ diff --git a/src/main/resources/assets/knockdowns/textures/gui/down_arrow.png b/src/main/resources/assets/knockdowns/textures/gui/down_arrow.png new file mode 100644 index 0000000..44a550d Binary files /dev/null and b/src/main/resources/assets/knockdowns/textures/gui/down_arrow.png differ diff --git a/src/main/resources/assets/knockdowns/textures/gui/knocked_icon.png b/src/main/resources/assets/knockdowns/textures/gui/knocked_icon.png new file mode 100755 index 0000000..d61f41c Binary files /dev/null and b/src/main/resources/assets/knockdowns/textures/gui/knocked_icon.png differ diff --git a/src/main/resources/assets/knockdowns/textures/gui/left_arrow.png b/src/main/resources/assets/knockdowns/textures/gui/left_arrow.png new file mode 100644 index 0000000..4badc50 Binary files /dev/null and b/src/main/resources/assets/knockdowns/textures/gui/left_arrow.png differ diff --git a/src/main/resources/assets/knockdowns/textures/gui/right_arrow.png b/src/main/resources/assets/knockdowns/textures/gui/right_arrow.png new file mode 100644 index 0000000..d62e7ca Binary files /dev/null and b/src/main/resources/assets/knockdowns/textures/gui/right_arrow.png differ diff --git a/src/main/resources/assets/knockdowns/textures/gui/up_arrow.png b/src/main/resources/assets/knockdowns/textures/gui/up_arrow.png new file mode 100644 index 0000000..56e803b Binary files /dev/null and b/src/main/resources/assets/knockdowns/textures/gui/up_arrow.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 7d30553..402f70a 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,16 +1,12 @@ -[ -{ - "modid": "examplemod", - "name": "Example Mod", - "description": "Example placeholder mod.", - "version": "${version}", - "mcversion": "${mcversion}", - "url": "", - "updateUrl": "", - "authorList": ["CleanroomMC"], - "credits": "Authors of this project", - "logoFile": "", - "screenshots": [], - "dependencies": [] -} -] +[{ + "modid": "${mod_id}", + "name": "${mod_name}", + "version": "${mod_version}", + "mcversion": "1.12.2", + "description": "${mod_description}", + "authorList": ${mod_authors}, + "credits": "${mod_credits}", + "url": "${mod_url}", + "updateJSON": "${mod_update_json}", + "logoFile": "${mod_logo_path}" +}] \ No newline at end of file diff --git a/src/main/resources/mixins.knockdowns.json b/src/main/resources/mixins.knockdowns.json new file mode 100644 index 0000000..7e58f0a --- /dev/null +++ b/src/main/resources/mixins.knockdowns.json @@ -0,0 +1,11 @@ +{ + "package": "ru.octol1ttle.knockdowns.common.mixins", + "required": true, + "refmap": "${mixin_refmap}", + "target": "@env(DEFAULT)", + "minVersion": "0.8.5", + "compatibilityLevel": "JAVA_8", + "mixins": [ "EntityLivingBaseMixin", "EntityPlayerMixin" ], + "server": [], + "client": [] +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 4018267..daa17a8 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,6 @@ { - "pack": { - "description": "examplemod resources", - "pack_format": 3, - "_comment": "A pack_format of 3 should be used starting with Minecraft 1.11. All resources, including language files, should be lowercase (eg: en_us.lang). A pack_format of 2 will load your mod resources with LegacyV2Adapter, which requires language files to have uppercase letters (eg: en_US.lang)." - } -} + "pack": { + "description": "${mod_name} Resources", + "pack_format": 3 + } +} \ No newline at end of file diff --git a/tags.properties b/tags.properties new file mode 100644 index 0000000..d795ef9 --- /dev/null +++ b/tags.properties @@ -0,0 +1,3 @@ +VERSION = ${mod_version} +MOD_ID = ${mod_id} +MOD_NAME = ${mod_name} \ No newline at end of file