Compare commits

...

10 commits

Author SHA1 Message Date
ac41344a3c
fix all of the bullshit!!
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
2024-07-15 13:18:14 +05:00
5fee6404b0
fixup: thanks git 2024-07-15 11:25:37 +05:00
f1c52e4371
1.0.0 2024-07-15 11:22:53 +05:00
Rongmario
a91692ded7
useJUnitPlatform + show_testing_output option 2024-04-27 00:48:57 +01:00
Rongmario
4cf81e171c
Enable testing 2024-04-27 00:25:58 +01:00
Riley Brown
83757cd9bc
Update Wiki link for issue #25 (#26) 2024-04-08 20:20:17 +07:00
Rongmario
c82fcee8aa
Merge overhaul branch to be upstream (#24)
* Debug mode for publishing artifacts
* Jabel
* Generation of mod meta, pack meta and mixin jsons
* Fixed runObfServer using 1.7's main class rather than 1.12's
- Allows changing of source without it being regenerated in dev
* ExampleMod + fixing tag collection
* Template expanding for mcmod.info + pack.mcmeta + remove redundant tasks
- Now supports arbitrary script blocks to retrieve value `${{ }}` from directly in gradle.properties
* Deployment via tasks/actions + changelog support + script folder
* Fixed mixin json generating condition
* Fix ATs not being applied
* Allow mixinbooter & configanytime to be prioritized in obf runs
* Remove redundant coremod arg addition as manifest is read at runtime
* Allow processResources to work correctly
* refactor: make parser changelog as method instead of job
* fix: ensure correct header parser for changelog (2to2 and 3to3)
* fix: no env available due to Github don't automatic inject env value to GHA
* refactor: standardize mod version with SemVer, remove unnecessary changelog block in `build.gradle`
* refactor: mixin config template and generator, resource filter
* Updated Gradle to 8.7 + RetroFuturaGradle to 1.3.35
* Update MixinBooter to 9.1 + provide wiki link

Co-authored-by: Oganesson897 <101081378+Darknight123MC@users.noreply.github.com>
Co-authored-by: Li <nhatlinh.l195@gmail.com>
Co-authored-by: Li <li.hvktqs@gmail.com>
2024-04-07 18:39:25 +01:00
Rongmario
7db468db1e Update RFG to 1.3.27 and update the maven url also 2024-01-09 02:28:31 +00:00
Rongmario
3102d3c033
Include RFG-compatible MinecraftDev fork's link in the README 2023-11-03 12:36:00 +00:00
Rongmario
5276e52f45 Formatting + some extra documentation 2023-05-28 03:55:27 +01:00
60 changed files with 2694 additions and 195 deletions

61
.github/workflows/deploy.yml vendored Normal file
View file

@ -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

6
CHANGELOG.md Normal file
View file

@ -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/)

18
LICENSE
View file

@ -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.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -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).

View file

@ -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'

View file

@ -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 =
# 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

View file

@ -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
// }
}

View file

@ -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'

View file

@ -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<String> propertyStringList(String key) {
return propertyStringList(key, ' ')
}
Collection<String> 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!")
}
}

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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()

View file

@ -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.

View file

@ -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> 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;
}
}

View file

@ -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<Integer, Callout> callouts = new HashMap<>();
public static Set<Map.Entry<Integer, Callout>> 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();
}
}

View file

@ -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<KnockedPlayerData> knockedDatas = new ArrayList<>();
public static void addKnockedNotification(int playerId, Vec3d position) {
knockedDatas.add(new KnockedPlayerData(playerId, position, client.world.getTotalWorldTime()));
}
public static Collection<KnockedPlayerData> getKnockedPlayerDatas() {
return knockedDatas;
}
public static void clearDatas() {
knockedDatas.clear();
}
}

View file

@ -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<EntityPlayer> 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();
}
}

View file

@ -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<KeyBinding, Supplier<CalloutType>> 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;
}
}
}
}

View file

@ -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<Integer, Callout> 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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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> T handleMessage(IMessage message);
class Dummy implements IClientProxy {
@Override
public void onFMLInit(FMLInitializationEvent event) {
}
@Override
public <T> T handleMessage(IMessage message) {
return null;
}
}
}

View file

@ -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<SoundEvent> event) {
event.getRegistry().register(KnockdownsSoundEvents.CALLOUT);
}
@SubscribeEvent
public static void onCapabilitiesAttach(AttachCapabilitiesEvent<Entity> 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<EntityPlayer> 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<EntityPlayer> 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;
}
}
}
}

View file

@ -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<String, Object> data) {
}
@Override
public String getAccessTransformerClass() {
return null;
}
@Override
public List<String> getMixinConfigs() {
ArrayList<String> list = new ArrayList<>();
list.add("mixins.knockdowns.json");
return list;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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<NBTTagCompound> {
boolean isKnockedDown();
void setKnockedDown(boolean knockedDown);
int getReviveTimeLeft();
void setReviveTimeLeft(int reviveTimeLeft);
int getTicksKnocked();
void setTicksKnocked(int ticksKnocked);
UniqueOnlyList<EntityPlayer> getRevivers();
static IKnockdownsPlayerData get(EntityPlayer player) {
return Objects.requireNonNull(player.getCapability(KnockdownsCapability.CAPABILITY, null));
}
}

View file

@ -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<NBTTagCompound> {
@CapabilityInject(IKnockdownsPlayerData.class)
public static Capability<IKnockdownsPlayerData> CAPABILITY;
public static final ResourceLocation ID = new ResourceLocation(Tags.MOD_ID, "data");
private KnockdownsPlayerData playerData = null;
private final LazyLoadBase<KnockdownsPlayerData> lazy = new LazyLoadBase<KnockdownsPlayerData>() {
@Override
protected KnockdownsPlayerData load() {
return playerData == null ? (playerData = new KnockdownsPlayerData()) : playerData;
}
};
public static void register() {
CapabilityManager.INSTANCE.register(IKnockdownsPlayerData.class, new Capability.IStorage<IKnockdownsPlayerData>() {
@Nullable
@Override
public NBTBase writeNBT(Capability<IKnockdownsPlayerData> capability, IKnockdownsPlayerData instance, EnumFacing side) {
return instance.serializeNBT();
}
@Override
public void readNBT(Capability<IKnockdownsPlayerData> 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> T getCapability(@Nonnull Capability<T> 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);
}
}

View file

@ -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<EntityPlayer> 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<EntityPlayer> 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);
}
}

View file

@ -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<Boolean> 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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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<IMessage, IMessage> 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<EntityPlayer> 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);
}
}

View file

@ -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<PlayerCalloutC2SPacket, IMessage> {
@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<CancelReviveC2SPacket, IMessage> {
@Override
public IMessage onMessage(CancelReviveC2SPacket message, MessageContext ctx) {
EntityPlayerMP player = ctx.getServerHandler().player;
for (EntityPlayer knocked : player.world.playerEntities) {
List<EntityPlayer> 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;
}
}
}

View file

@ -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) {
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,23 @@
package ru.octol1ttle.knockdowns.common.util;
import java.util.ArrayList;
public class UniqueOnlyList<T> extends ArrayList<T> {
@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();
}
}

View file

@ -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

View file

@ -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 был обречён на тяжелое ранение

View file

@ -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"
}
]
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -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}"
}]

View file

@ -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": []
}

View file

@ -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
}
}

3
tags.properties Normal file
View file

@ -0,0 +1,3 @@
VERSION = ${mod_version}
MOD_ID = ${mod_id}
MOD_NAME = ${mod_name}