├── minecraft ├── neoforge │ ├── gradle.properties │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ ├── services │ │ │ │ ├── io.github.kosmx.emotes.server.services.InstanceService │ │ │ │ ├── io.github.kosmx.emotes.mc.services.IPermissionService │ │ │ │ ├── io.github.kosmx.emotes.arch.network.NetworkPlatformTools │ │ │ │ └── io.github.kosmx.emotes.arch.network.client.ClientNetwork │ │ │ │ └── neoforge.mods.toml │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kosmx │ │ │ └── emotes │ │ │ ├── neoforge │ │ │ ├── executor │ │ │ │ └── ForgeEmotesMain.java │ │ │ ├── EmotecraftNeoMod.java │ │ │ ├── services │ │ │ │ └── NeoPermissionService.java │ │ │ └── EmotecraftClientNeoMod.java │ │ │ └── arch │ │ │ └── network │ │ │ ├── client │ │ │ └── neoforge │ │ │ │ └── ClientNetworkImpl.java │ │ │ └── neoforge │ │ │ └── NetworkPlatformToolsImpl.java │ └── ABOUT.md ├── fabric │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── services │ │ │ │ │ ├── io.github.kosmx.emotes.server.services.InstanceService │ │ │ │ │ ├── io.github.kosmx.emotes.mc.services.IPermissionService │ │ │ │ │ ├── io.github.kosmx.emotes.arch.network.NetworkPlatformTools │ │ │ │ │ └── io.github.kosmx.emotes.arch.network.client.ClientNetwork │ │ │ └── fabric.mod.json │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kosmx │ │ │ └── emotes │ │ │ ├── fabric │ │ │ ├── ModMenu.java │ │ │ ├── executor │ │ │ │ └── FabricEmotesMain.java │ │ │ ├── services │ │ │ │ └── FabricPermissionService.java │ │ │ ├── EmotecraftClientFabricMod.java │ │ │ ├── network │ │ │ │ ├── PayloadTypeRegistator.java │ │ │ │ ├── ClientNetworkInstance.java │ │ │ │ └── ServerNetworkStuff.java │ │ │ └── EmotecraftFabricMod.java │ │ │ └── arch │ │ │ └── network │ │ │ ├── client │ │ │ └── fabric │ │ │ │ └── ClientNetworkImpl.java │ │ │ └── fabric │ │ │ └── NetworkPlatformToolsImpl.java │ └── ABOUT.md ├── archCommon │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── services │ │ │ │ │ ├── io.github.kosmx.emotes.api.events.client.ClientEmoteAPI │ │ │ │ │ └── io.github.kosmx.emotes.api.events.server.ServerEmoteAPI │ │ │ ├── architectury.common.json │ │ │ ├── emotecraft-arch.mixins.json │ │ │ └── emotes.accesswidener │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kosmx │ │ │ └── emotes │ │ │ ├── main │ │ │ ├── emotePlay │ │ │ │ ├── package-info.java │ │ │ │ ├── instances │ │ │ │ │ ├── EmotecraftSoundEvents.java │ │ │ │ │ ├── SoundEventInstance.java │ │ │ │ │ └── SoundDirectInstance.java │ │ │ │ └── MinecraftNbsPlayer.java │ │ │ ├── network │ │ │ │ └── package-info.java │ │ │ ├── EmotecraftMod.java │ │ │ └── mixinFunctions │ │ │ │ └── IPlayerEntity.java │ │ │ ├── arch │ │ │ ├── network │ │ │ │ ├── EmotesMixinNetwork.java │ │ │ │ ├── EmotesMixinConnection.java │ │ │ │ ├── AvatarServerPlayNetwork.java │ │ │ │ ├── ConfigTask.java │ │ │ │ ├── ModdedServerPlayNetwork.java │ │ │ │ ├── EmotePacketPayload.java │ │ │ │ ├── AbstractServerNetwork.java │ │ │ │ └── NetworkPlatformTools.java │ │ │ ├── mixin │ │ │ │ ├── ServerCommonPacketListenerAccessor.java │ │ │ │ ├── KeyEventMixin.java │ │ │ │ ├── ConnectionHandlerMixin.java │ │ │ │ ├── LivingEntityRendererMixin.java │ │ │ │ ├── AvatarAnimManagerMixin.java │ │ │ │ ├── EntityRenderDispatcherMixin.java │ │ │ │ ├── SoundEngineMixin.java │ │ │ │ ├── ServerPlayNetworkMixin.java │ │ │ │ └── PlayerMixin.java │ │ │ ├── screen │ │ │ │ ├── widget │ │ │ │ │ ├── FastChooseController.java │ │ │ │ │ ├── IChooseElement.java │ │ │ │ │ └── preview │ │ │ │ │ │ └── elemets │ │ │ │ │ │ ├── PlayerChooseSquareElement.java │ │ │ │ │ │ └── PlayerChooseCircleElement.java │ │ │ │ ├── utils │ │ │ │ │ ├── WidgetOutliner.java │ │ │ │ │ ├── EmotecraftTexture.java │ │ │ │ │ ├── PageButton.java │ │ │ │ │ ├── UnsafeMannequin.java │ │ │ │ │ └── EmoteListener.java │ │ │ │ └── ingame │ │ │ │ │ └── FullMenuScreen.java │ │ │ ├── gui │ │ │ │ └── widgets │ │ │ │ │ └── search │ │ │ │ │ ├── ISearchEngine.java │ │ │ │ │ └── VanillaSearch.java │ │ │ ├── EmotecraftClientMod.java │ │ │ └── ClientCommands.java │ │ │ └── PlatformTools.java │ └── build.gradle.kts └── build.gradle.kts ├── emotesAssets ├── src │ └── main │ │ └── resources │ │ ├── assets │ │ └── emotecraft │ │ │ ├── lang │ │ │ ├── af_ZA.json │ │ │ ├── da_DK.json │ │ │ ├── el_GR.json │ │ │ ├── nl_NL.json │ │ │ ├── sr_SP.json │ │ │ ├── vi_VN.json │ │ │ ├── no_NO.json │ │ │ ├── sv_SE.json │ │ │ ├── en_PT.json │ │ │ ├── fi_FI.json │ │ │ ├── he_IL.json │ │ │ └── ar_SA.json │ │ │ ├── emotes │ │ │ ├── clap.png │ │ │ ├── here.png │ │ │ ├── palm.png │ │ │ ├── crying.png │ │ │ ├── point.png │ │ │ ├── twerk.png │ │ │ ├── waving.png │ │ │ ├── backflip.png │ │ │ ├── kazotsky_kick.png │ │ │ ├── club_penguin_dance.png │ │ │ ├── roblox_potion_dance.png │ │ │ └── waving.json │ │ │ └── textures │ │ │ ├── folder.png │ │ │ └── folder_open.png │ │ └── emotecraft_mod_logo.png ├── ABOUT.md └── build.gradle.kts ├── .gitattributes ├── blender ├── emote_creator.blend ├── emote_creator_bend.blend ├── emote_creator_bend_item_scale.blend └── README.md ├── blend and other files ├── icon.png ├── icon.blend ├── emote.blend ├── point.blend ├── icon_small.png ├── over here.blend ├── Over here_wr.blend ├── emote_creator.blend └── example_emote.json ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── emotesMc ├── ABOUT.md ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── io.github.kosmx.emotes.mc.services.IPermissionService │ │ └── java │ │ └── io │ │ └── github │ │ └── kosmx │ │ └── emotes │ │ └── mc │ │ ├── services │ │ ├── impl │ │ │ └── VanillaPermissionService.java │ │ └── IPermissionService.java │ │ └── McUtils.java └── build.gradle.kts ├── paper └── src │ └── main │ ├── resources │ ├── META-INF │ │ └── services │ │ │ ├── io.github.kosmx.emotes.api.events.server.ServerEmoteAPI │ │ │ └── io.github.kosmx.emotes.mc.services.IPermissionService │ └── paper-plugin.yml │ └── java │ └── io │ └── github │ └── kosmx │ └── emotes │ └── bukkit │ ├── services │ └── BukkitPermissionService.java │ ├── network │ └── BukkitNetworkInstance.java │ └── fuckery │ └── StreamCodecUtils.java ├── emotesServer ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ ├── io.github.kosmx.emotes.server.serializer.type.IWriter │ │ │ ├── io.github.kosmx.emotes.server.services.InstanceService │ │ │ └── io.github.kosmx.emotes.server.serializer.type.IReader │ │ └── java │ │ └── io │ │ └── github │ │ └── kosmx │ │ └── emotes │ │ └── server │ │ ├── serializer │ │ ├── type │ │ │ ├── ISerializer.java │ │ │ ├── IReader.java │ │ │ ├── IWriter.java │ │ │ ├── EmoteSerializerException.java │ │ │ └── impl │ │ │ │ ├── QuarkReaderWrapper.java │ │ │ │ └── BinaryFormat.java │ │ └── EmoteWriter.java │ │ ├── network │ │ ├── IServerNetworkInstance.java │ │ └── EmotePlayTracker.java │ │ ├── services │ │ ├── impl │ │ │ └── InstanceServiceImpl.java │ │ └── InstanceService.java │ │ └── config │ │ └── ConfigSerializer.java ├── ABOUT.md └── build.gradle.kts ├── emotesAPI ├── ABOUT.md ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kosmx │ │ │ └── emotes │ │ │ ├── api │ │ │ ├── services │ │ │ │ └── IEmotecraftService.java │ │ │ └── events │ │ │ │ └── client │ │ │ │ ├── ClientNetworkEvents.java │ │ │ │ └── ClientEmoteAPI.java │ │ │ └── common │ │ │ ├── network │ │ │ ├── objects │ │ │ │ ├── NetHashMap.java │ │ │ │ ├── AbstractNetworkPacket.java │ │ │ │ ├── StopPacket.java │ │ │ │ ├── PlayerDataPacket.java │ │ │ │ ├── NewAnimPacket.java │ │ │ │ ├── EmoteIconPacket.java │ │ │ │ ├── EmoteDataPacket.java │ │ │ │ ├── DiscoveryPacket.java │ │ │ │ ├── EmoteHeaderPacket.java │ │ │ │ └── SongPacket.java │ │ │ ├── package-info.java │ │ │ ├── PacketTask.java │ │ │ ├── PacketConfig.java │ │ │ └── CommonNetwork.java │ │ │ ├── tools │ │ │ ├── MathHelper.java │ │ │ ├── UUIDMap.java │ │ │ └── ServiceLoaderUtil.java │ │ │ ├── nbsplayer │ │ │ └── NbsPlayer.java │ │ │ └── CommonData.java │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── kosmx │ │ └── emotes │ │ └── testing │ │ └── common │ │ ├── BiMapTest.java │ │ └── RandomEmoteData.java └── build.gradle.kts ├── GEYSERMC.md ├── .gitignore ├── SECURITY.md ├── other tools └── bytes size calculator.py ├── settings.gradle.kts ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── crowdin.yml │ ├── build_test.yml │ ├── publish.yml │ └── codeql-analysis.yml ├── gradle.properties └── gradlew.bat /minecraft/neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform = neoforge -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/af_ZA.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/da_DK.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/el_GR.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/nl_NL.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/sr_SP.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/vi_VN.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /blender/emote_creator.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blender/emote_creator.blend -------------------------------------------------------------------------------- /blend and other files/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/icon.png -------------------------------------------------------------------------------- /blend and other files/icon.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/icon.blend -------------------------------------------------------------------------------- /blender/emote_creator_bend.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blender/emote_creator_bend.blend -------------------------------------------------------------------------------- /blend and other files/emote.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/emote.blend -------------------------------------------------------------------------------- /blend and other files/point.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/point.blend -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /blend and other files/icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/icon_small.png -------------------------------------------------------------------------------- /blend and other files/over here.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/over here.blend -------------------------------------------------------------------------------- /blend and other files/Over here_wr.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/Over here_wr.blend -------------------------------------------------------------------------------- /blend and other files/emote_creator.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blend and other files/emote_creator.blend -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/no_NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "emotecraft.options.options": "Mod innstillinger" 3 | } 4 | -------------------------------------------------------------------------------- /blender/emote_creator_bend_item_scale.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/blender/emote_creator_bend_item_scale.blend -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/emotecraft_mod_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/emotecraft_mod_logo.png -------------------------------------------------------------------------------- /emotesMc/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### emotesMc 2 | `emotesMc` module contains common serverside Minecraft code 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesMc:` -------------------------------------------------------------------------------- /paper/src/main/resources/META-INF/services/io.github.kosmx.emotes.api.events.server.ServerEmoteAPI: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.bukkit.network.ServerSideEmotePlay -------------------------------------------------------------------------------- /paper/src/main/resources/META-INF/services/io.github.kosmx.emotes.mc.services.IPermissionService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.bukkit.services.BukkitPermissionService -------------------------------------------------------------------------------- /emotesMc/src/main/resources/META-INF/services/io.github.kosmx.emotes.mc.services.IPermissionService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.mc.services.impl.VanillaPermissionService -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/META-INF/services/io.github.kosmx.emotes.server.services.InstanceService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.fabric.executor.FabricEmotesMain -------------------------------------------------------------------------------- /emotesServer/src/main/resources/META-INF/services/io.github.kosmx.emotes.server.serializer.type.IWriter: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.server.serializer.type.impl.BinaryFormat -------------------------------------------------------------------------------- /emotesServer/src/main/resources/META-INF/services/io.github.kosmx.emotes.server.services.InstanceService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.server.services.impl.InstanceServiceImpl -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/resources/META-INF/services/io.github.kosmx.emotes.api.events.client.ClientEmoteAPI: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.main.network.ClientEmotePlay -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/resources/META-INF/services/io.github.kosmx.emotes.server.services.InstanceService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.neoforge.executor.ForgeEmotesMain -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/META-INF/services/io.github.kosmx.emotes.mc.services.IPermissionService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.fabric.services.FabricPermissionService -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/resources/META-INF/services/io.github.kosmx.emotes.mc.services.IPermissionService: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.neoforge.services.NeoPermissionService -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/clap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/clap.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/here.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/here.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/palm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/palm.png -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/resources/META-INF/services/io.github.kosmx.emotes.api.events.server.ServerEmoteAPI: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.arch.network.CommonServerNetworkHandler -------------------------------------------------------------------------------- /minecraft/fabric/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### minecraft:fabric 2 | `fabric` module contains actual implementation in Fabric 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesFabric:` -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/META-INF/services/io.github.kosmx.emotes.arch.network.NetworkPlatformTools: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.arch.network.fabric.NetworkPlatformToolsImpl -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/META-INF/services/io.github.kosmx.emotes.arch.network.client.ClientNetwork: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.arch.network.client.fabric.ClientNetworkImpl -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/crying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/crying.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/point.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/twerk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/twerk.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/waving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/waving.png -------------------------------------------------------------------------------- /minecraft/neoforge/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### minecraft:neoforge 2 | `neoforge` module contains actual implementation in NeoForge 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesNeo:` -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/resources/META-INF/services/io.github.kosmx.emotes.arch.network.NetworkPlatformTools: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.arch.network.neoforge.NetworkPlatformToolsImpl -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/resources/META-INF/services/io.github.kosmx.emotes.arch.network.client.ClientNetwork: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.arch.network.client.neoforge.ClientNetworkImpl -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/backflip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/backflip.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/textures/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/textures/folder.png -------------------------------------------------------------------------------- /emotesServer/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### emotesServer 2 | `emotesServer` contains server-side logic that does not use Minecraft code 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesServer:` -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/emotePlay/package-info.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.emotePlay; 2 | 3 | /* 4 | Everything emote play related 5 | */ -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/kazotsky_kick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/kazotsky_kick.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/textures/folder_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/textures/folder_open.png -------------------------------------------------------------------------------- /emotesAssets/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### emotesAssets 2 | `emotesAssets` contains common assets for plugin & mod, like built-in emotes and language files 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesAssets:` -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/club_penguin_dance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/club_penguin_dance.png -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/roblox_potion_dance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosmX/emotes/HEAD/emotesAssets/src/main/resources/assets/emotecraft/emotes/roblox_potion_dance.png -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/network/package-info.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.network; 2 | 3 | /* 4 | The client network core. Packet sending logic, receiving, sending stuff... 5 | */ -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "injected_interfaces": { 3 | "net/minecraft/class_11890": [ 4 | "io/github/kosmx/emotes/main/mixinFunctions/IPlayerEntity" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/sv_SE.json: -------------------------------------------------------------------------------- 1 | { 2 | "emotecraft.options.options": "Modinställningar", 3 | "key.category.emotecraft.keybinding": "Emotecraft", 4 | "emotecraft.emote.author": "Skapare: ", 5 | "emotecraft.blockedEmote": "Du får inte använda den här emoten." 6 | } 7 | -------------------------------------------------------------------------------- /emotesServer/src/main/resources/META-INF/services/io.github.kosmx.emotes.server.serializer.type.IReader: -------------------------------------------------------------------------------- 1 | io.github.kosmx.emotes.server.serializer.type.impl.QuarkReaderWrapper 2 | io.github.kosmx.emotes.server.serializer.type.impl.BinaryFormat 3 | io.github.kosmx.emotes.server.serializer.type.impl.JsonEmoteWrapper -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /paper/src/main/resources/paper-plugin.yml: -------------------------------------------------------------------------------- 1 | name: emotecraft 2 | description: "${description}" 3 | main: io.github.kosmx.emotes.bukkit.BukkitWrapper 4 | version: ${version} 5 | api-version: ${mcversion} 6 | website: https://docs.zigythebird.com/emotecraft/gettingstarted 7 | authors: [KomsX, dima_dencep] 8 | folia-supported: true 9 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/EmotesMixinNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public interface EmotesMixinNetwork { 6 | @NotNull 7 | ModdedServerPlayNetwork emotecraft$getServerNetworkInstance(); 8 | } 9 | -------------------------------------------------------------------------------- /emotesAPI/ABOUT.md: -------------------------------------------------------------------------------- 1 | ### emotesAPI 2 | Emotecraft common package. 3 | 4 | Dependency: `io.github.kosmx.emotes:emotesAPI:` 5 | 6 | --- 7 | packet serialization, some verification logic, 8 | Everything what can be done without ANY Minecraft 9 |
10 | To work on Bukkit and Collar. 11 | 12 | Base api will be in this module 13 | -------------------------------------------------------------------------------- /GEYSERMC.md: -------------------------------------------------------------------------------- 1 | # GeyserMC compatibility 2 | **There should be a tutorial, how to set-up Geyser** 3 | *maybe later* 4 | 5 | 6 | ## Warnings! 7 | 8 | Emotecraft has a pretty complex logic to play emotes for people joining after the emote play started. 9 | BE has nothing to do with it. Don't make long emotes for Bedrock users. 10 | 11 | Also BE can not stop an emote. 12 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/ISerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type; 2 | 3 | import io.github.kosmx.emotes.api.services.IEmotecraftService; 4 | 5 | public interface ISerializer extends IEmotecraftService { 6 | String getExtension(); 7 | 8 | @Override 9 | default boolean isActive() { 10 | return getExtension() != null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/EmotesMixinConnection.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Map; 7 | 8 | public interface EmotesMixinConnection { 9 | @NotNull 10 | Map emotecraft$getRemoteVersions(); 11 | 12 | void emotecraft$setVersions(@Nullable Map map); 13 | } 14 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/api/services/IEmotecraftService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.api.services; 2 | 3 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 4 | 5 | public interface IEmotecraftService { 6 | boolean isActive(); 7 | 8 | default int getPriority() { 9 | return ServiceLoaderUtil.DEFAULT_PRIORITY; 10 | } 11 | 12 | default String getName() { 13 | return getClass().getName(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/network/IServerNetworkInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.network; 2 | 3 | import io.github.kosmx.emotes.api.proxy.INetworkInstance; 4 | 5 | public interface IServerNetworkInstance extends INetworkInstance { 6 | /** 7 | * Server closes connection with instance 8 | */ 9 | default void closeConnection() {} 10 | 11 | default boolean trackPlayState() { 12 | return true; 13 | } 14 | 15 | EmotePlayTracker getEmoteTracker(); 16 | } 17 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/en_PT.json: -------------------------------------------------------------------------------- 1 | { 2 | "emotecraft.otherconfig.debug": "Fly Swatter", 3 | "key.emotecraft.fastchoose": "Room o' Quick", 4 | "key.emotecraft.debug": "use test taunt", 5 | "key.emotecraft.stop": "Stop taunting enemy", 6 | "emotecraft.openFolder": "Enter room o' emotes", 7 | "emotecraft.otherconfig.quark": "Open Quarklands treasure", 8 | "key.category.emotecraft.keybinding": "Emotecraft", 9 | "emotecraft.emote.author": "Captain: ", 10 | "emotecraft.config": "Choose taunts" 11 | } 12 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/ServerCommonPacketListenerAccessor.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import net.minecraft.network.Connection; 4 | import net.minecraft.server.network.ServerCommonPacketListenerImpl; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ServerCommonPacketListenerImpl.class) 9 | public interface ServerCommonPacketListenerAccessor { 10 | @Accessor() 11 | Connection getConnection(); 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Gradle 2 | .gradle/ 3 | build/ 4 | out/ 5 | classes/ 6 | .out/ 7 | 8 | #IDEA 9 | .idea/ 10 | *.iml 11 | *.ipr 12 | *.iws 13 | 14 | #vscode 15 | .settings/ 16 | .vscode/ 17 | bin/ 18 | .classpath 19 | .project 20 | 21 | #fabric 22 | run/ 23 | 24 | #project junk 25 | quark api.txt 26 | icon.blend1 27 | blender/emote_creator - Copy.blend1 28 | blender/emote_creator - Copy.blend 29 | blender/emote.json 30 | 31 | crowdin_old.yml 32 | 33 | *.blend1 34 | /.architectury-transformer/* 35 | /artifacts 36 | /buildSrc/.kotlin/ 37 | .DS_Store 38 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/widget/FastChooseController.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.widget; 2 | 3 | import net.minecraft.client.input.MouseButtonEvent; 4 | import net.minecraft.client.input.MouseButtonInfo; 5 | 6 | public interface FastChooseController { 7 | boolean doHoverPart(IChooseElement part); 8 | boolean isValidClickButton(MouseButtonInfo info); 9 | boolean onClick(IChooseElement element, MouseButtonEvent event, boolean bl); 10 | boolean doesShowInvalid(); 11 | } 12 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/NetHashMap.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import it.unimi.dsi.fastutil.bytes.Byte2ObjectLinkedOpenHashMap; 4 | 5 | public class NetHashMap extends Byte2ObjectLinkedOpenHashMap { 6 | public NetHashMap(AbstractNetworkPacket... packets) { 7 | for (AbstractNetworkPacket packet : packets) put(packet); 8 | } 9 | 10 | public void put(AbstractNetworkPacket packet) { 11 | put(packet.getID(), packet); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/ModMenu.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import io.github.kosmx.emotes.arch.screen.EmoteMenu; 6 | import net.minecraft.client.gui.screens.Screen; 7 | 8 | public class ModMenu implements ModMenuApi { 9 | 10 | @Override 11 | public ConfigScreenFactory getModConfigScreenFactory() { 12 | return (ConfigScreenFactory) EmoteMenu::new; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/IReader.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | 5 | import java.io.InputStream; 6 | import java.util.Map; 7 | 8 | public interface IReader extends ISerializer { 9 | Map read(InputStream reader, String filename) throws EmoteSerializerException; 10 | 11 | default boolean canRead(String fileName) { 12 | return fileName != null && fileName.endsWith("." + getExtension()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | Packet part IDs 4 | 5 | 0 - emoteData v0 6 | 1 - PlayerData v0 7 | 8 - configExchange (Discovery) v8- its version is the common network version 8 | 10 - stopData v0 9 | 0x11 - EmoteHeader 10 | 0x12 - EmoteIcon 11 | 12 | configuration keys can be found in {@link io.github.kosmx.emotes.common.network.PacketConfig} 13 | 14 | 3 - song -> ver0: no sound, ver1 current 15 | 16 | ID > 0x80 - config bits 17 | 18 | 0x80 does server track play states [bool] 19 | 20 | */ 21 | package io.github.kosmx.emotes.common.network; 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | |---------|--------------------| 7 | | 1.4.x | :x: | 8 | | 2.0.x | :x: | 9 | | 2.4.x | :x: | 10 | | 2.5.x | :x: | 11 | | 2.6.x | :x: | 12 | | 3.0.x | :x: | 13 | | 3.1.x | :warning: | 14 | | 3.2.x | :white_check_mark: | 15 | ## Reporting a Vulnerability 16 | 17 | If you find a security vulnerability affecting this mod, immediately open an issue or contact me on Discord. 18 | Even if it affects only older versions 19 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/IWriter.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | 5 | import java.io.OutputStream; 6 | 7 | public interface IWriter extends ISerializer { 8 | void write(Animation emote, OutputStream writer, String filename) throws EmoteSerializerException; 9 | 10 | boolean onlyEmoteFile(); 11 | boolean possibleDataLoss(); 12 | 13 | default boolean canWrite(String fileName) { 14 | return fileName != null && fileName.endsWith("." + getExtension()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/widget/IChooseElement.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.widget; 2 | 3 | import io.github.kosmx.emotes.main.EmoteHolder; 4 | import net.minecraft.client.gui.components.Renderable; 5 | import net.minecraft.client.gui.components.events.GuiEventListener; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface IChooseElement extends GuiEventListener, Renderable { 9 | boolean hasEmote(); 10 | @Nullable 11 | EmoteHolder getEmote(); 12 | void clearEmote(); 13 | void setEmote(EmoteHolder emote); 14 | 15 | void removed(); 16 | } 17 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/resources/emotecraft-arch.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.kosmx.emotes.arch.mixin", 5 | "compatibilityLevel": "JAVA_16", 6 | "mixins": [ 7 | "ConnectionHandlerMixin", 8 | "AvatarAnimManagerMixin", 9 | "ServerCommonPacketListenerAccessor", 10 | "ServerPlayNetworkMixin" 11 | ], 12 | "client": [ 13 | "EmoteAvatarMixin", 14 | "EntityRenderDispatcherMixin", 15 | "KeyEventMixin", 16 | "LivingEntityRendererMixin", 17 | "PlayerMixin", 18 | "SoundEngineMixin" 19 | ], 20 | "server": [], 21 | "injectors": { 22 | "defaultRequire": 1 23 | } 24 | } -------------------------------------------------------------------------------- /other tools/bytes size calculator.py: -------------------------------------------------------------------------------- 1 | # This script is for calculating size of known data structures 2 | 3 | if __name__ == '__main__': 4 | inp = input().upper() 5 | sizeSum = 0 6 | for i in inp: 7 | if i == 'B': # B as byte or byte sized boolean 8 | sizeSum += 1 9 | elif i == 'S' or i == 'C': # S as short C as char 10 | sizeSum += 2 11 | elif i == 'I' or i == 'F': # I as int F as float 12 | sizeSum += 4 13 | elif i == 'L' or i == 'D': # L as long int D as double 14 | sizeSum += 8 15 | else: 16 | print("I don't know what to do with \"{}\".".format(i)) 17 | print(sizeSum) 18 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven("https://maven.fabricmc.net/") 4 | maven("https://maven.architectury.dev/") 5 | maven("https://maven.neoforged.net/releases") 6 | maven("https://repo.redlance.org/public") 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | rootProject.name = "emotecraft" 12 | 13 | include("emotesAPI") 14 | include("emotesServer") 15 | include("emotesAssets") 16 | include("emotesMc") 17 | 18 | //Minecraft 1.20 version 19 | include("minecraft") 20 | include("minecraft:archCommon") 21 | include("minecraft:fabric") 22 | include("minecraft:neoforge") 23 | 24 | // Paper plugin 25 | include("paper") 26 | -------------------------------------------------------------------------------- /emotesServer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | `maven-publish` 5 | } 6 | 7 | version = mod_version 8 | 9 | dependencies { 10 | api(project(":emotesAPI")) 11 | } 12 | 13 | java { 14 | withSourcesJar() 15 | } 16 | 17 | publishing { 18 | publications { 19 | register("mavenJava") { 20 | artifactId = "emotesServer" 21 | 22 | from(components["java"]) 23 | 24 | withCustomPom("emotesServer", "Minecraft Emotecraft server common module") 25 | } 26 | } 27 | 28 | repositories { 29 | if (shouldPublishMaven) { 30 | kosmxRepo(project) 31 | } else { 32 | mavenLocal() 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/EmoteSerializerException.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | public class EmoteSerializerException extends RuntimeException { 6 | @Nullable 7 | private final String type; 8 | 9 | public EmoteSerializerException(String msg, @Nullable String type) { 10 | super(msg); 11 | this.type = type; 12 | } 13 | 14 | public EmoteSerializerException(String msg, @Nullable String type, Throwable cause) { 15 | super(msg, cause); 16 | this.type = type; 17 | } 18 | 19 | @Nullable 20 | public String getType() { 21 | return this.type; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/services/impl/InstanceServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.services.impl; 2 | 3 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 4 | import io.github.kosmx.emotes.server.services.InstanceService; 5 | 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | public class InstanceServiceImpl implements InstanceService { 10 | @Override 11 | public Path getGameDirectory() { 12 | return Paths.get(""); 13 | } 14 | 15 | @Override 16 | public int getPriority() { 17 | return ServiceLoaderUtil.LOWEST_PRIORITY; 18 | } 19 | 20 | @Override 21 | public boolean isActive() { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/executor/FabricEmotesMain.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric.executor; 2 | 3 | import io.github.kosmx.emotes.server.services.InstanceService; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | 6 | import java.nio.file.Path; 7 | 8 | public class FabricEmotesMain implements InstanceService { 9 | @Override 10 | public Path getGameDirectory() { 11 | return FabricLoader.getInstance().getGameDir(); 12 | } 13 | 14 | @Override 15 | public Path getConfigPath() { 16 | return FabricLoader.getInstance().getConfigDir().resolve("emotecraft.json"); 17 | } 18 | 19 | @Override 20 | public boolean isActive() { 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/neoforge/executor/ForgeEmotesMain.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.neoforge.executor; 2 | 3 | import io.github.kosmx.emotes.server.services.InstanceService; 4 | import net.neoforged.fml.loading.FMLLoader; 5 | import net.neoforged.fml.loading.FMLPaths; 6 | 7 | import java.nio.file.Path; 8 | 9 | public class ForgeEmotesMain implements InstanceService { 10 | @Override 11 | public Path getGameDirectory() { 12 | return FMLLoader.getCurrent().getGameDir(); 13 | } 14 | 15 | @Override 16 | public Path getConfigPath() { 17 | return FMLPaths.CONFIGDIR.get().resolve("emotecraft.json"); 18 | } 19 | 20 | @Override 21 | public boolean isActive() { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/KeyEventMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import io.github.kosmx.emotes.main.EmoteHolder; 5 | import net.minecraft.client.KeyMapping; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(value = KeyMapping.class, priority = 2000) 12 | public class KeyEventMixin { 13 | @Inject(method = "click", at = @At(value = "HEAD")) 14 | private static void keyPressCallback(InputConstants.Key key, CallbackInfo ci){ 15 | EmoteHolder.handleKeyPress(key); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /paper/src/main/java/io/github/kosmx/emotes/bukkit/services/BukkitPermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.bukkit.services; 2 | 3 | import io.github.kosmx.emotes.mc.services.IPermissionService; 4 | import net.minecraft.commands.CommandSourceStack; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Optional; 8 | 9 | public class BukkitPermissionService implements IPermissionService { 10 | @Override 11 | public Optional getPermissionValue(@NotNull CommandSourceStack source, @NotNull String permission) { 12 | if (!source.isPlayer()) return Optional.empty(); 13 | return Optional.of(source.getBukkitSender().hasPermission(permission)); 14 | } 15 | 16 | @Override 17 | public boolean isActive() { 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/arch/network/client/fabric/ClientNetworkImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network.client.fabric; 2 | 3 | import io.github.kosmx.emotes.arch.network.client.ClientNetwork; 4 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 5 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 6 | import net.minecraft.resources.Identifier; 7 | 8 | @SuppressWarnings("unused") 9 | public final class ClientNetworkImpl extends ClientNetwork { 10 | @Override 11 | public boolean isServerChannelOpen(Identifier id) { 12 | return ClientPlayNetworking.canSend(id); 13 | } 14 | 15 | @Override 16 | public void sendPlayPacket(CustomPacketPayload payload) { 17 | ClientPlayNetworking.send(payload); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /emotesMc/src/main/java/io/github/kosmx/emotes/mc/services/impl/VanillaPermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.mc.services.impl; 2 | 3 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 4 | import io.github.kosmx.emotes.mc.services.IPermissionService; 5 | import net.minecraft.commands.CommandSourceStack; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Optional; 9 | 10 | public class VanillaPermissionService implements IPermissionService { 11 | @Override 12 | public Optional getPermissionValue(@NotNull CommandSourceStack source, @NotNull String permission) { 13 | return Optional.empty(); 14 | } 15 | 16 | @Override 17 | public boolean isActive() { 18 | return true; 19 | } 20 | 21 | @Override 22 | public int getPriority() { 23 | return ServiceLoaderUtil.LOWEST_PRIORITY; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/services/FabricPermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric.services; 2 | 3 | import io.github.kosmx.emotes.mc.services.IPermissionService; 4 | import me.lucko.fabric.api.permissions.v0.Permissions; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.minecraft.commands.CommandSourceStack; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Optional; 10 | 11 | public class FabricPermissionService implements IPermissionService { 12 | @Override 13 | public Optional getPermissionValue(@NotNull CommandSourceStack source, @NotNull String permission) { 14 | return Permissions.getPermissionValue(source, permission).map(b -> b); 15 | } 16 | 17 | @Override 18 | public boolean isActive() { 19 | return FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/arch/network/client/neoforge/ClientNetworkImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network.client.neoforge; 2 | 3 | import io.github.kosmx.emotes.arch.network.client.ClientNetwork; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 6 | import net.minecraft.resources.Identifier; 7 | import net.neoforged.neoforge.client.network.ClientPacketDistributor; 8 | import java.util.Objects; 9 | 10 | public final class ClientNetworkImpl extends ClientNetwork { 11 | @Override 12 | public boolean isServerChannelOpen(Identifier id) { 13 | return Objects.requireNonNull(Minecraft.getInstance().getConnection()).hasChannel(id); 14 | } 15 | 16 | @Override 17 | public void sendPlayPacket(CustomPacketPayload payload) { 18 | ClientPacketDistributor.sendToServer(payload); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Minecraft version+mod loader** 24 | - Version: [e.g. 1.19.2] 25 | - Modded: [e.g. Fabric 0.14.9] 26 | - Client/Server 27 | 28 | **Mod version**: [e.g. 2.1.3-fabric] 29 | 30 | **Minecraft log __and__ crash report**: 31 | [you might use services like mclo.gs] 32 | ``` 33 | log here 34 | ``` 35 | ``` 36 | crash report here 37 | ``` 38 | 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /blend and other files/example_emote.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Waving", 3 | "author": "KosmX", 4 | "description": "this is an example emote", 5 | "you can add": "comment like this", 6 | "emote":{ 7 | "beginTick":3, 8 | "endTick":70, 9 | "resetTick":75, 10 | "degrees":true, 11 | "moves":[ 12 | { 13 | "tick":12, 14 | "easing": "InOutSine", 15 | "head":{ 16 | "yaw":23, 17 | "pitch":115 18 | } 19 | }, 20 | { 21 | "tick":45, 22 | "easing": "OutBounce", 23 | "head":{ 24 | "yaw":27, 25 | "pitch":165 26 | }, 27 | "rightArm": { 28 | "pitch": 63 29 | } 30 | }, 31 | { 32 | "tick": 45, 33 | "easing": "linear", 34 | "turn": -1, 35 | "comment": "after tick 45 the right arm's yaw will set back with a full turn", 36 | "rightArm": { 37 | "yaw": 360, 38 | "comment": "you cannot define a value for the same tick twice (in this case pitch)" 39 | } 40 | } 41 | ] 42 | } 43 | } -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/tools/MathHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.tools; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.ByteBuffer; 6 | 7 | public class MathHelper { 8 | public static ByteBuffer readFromIStream(InputStream stream) throws IOException { 9 | return ByteBuffer.wrap(stream.readAllBytes()); 10 | } 11 | 12 | /** 13 | * If {@link ByteBuffer} is wrapped, it is safe to get the array 14 | * but if is direct manual read is required. 15 | * @param byteBuffer get the bytes from 16 | * @return the byte array 17 | */ 18 | public static byte[] safeGetBytesFromBuffer(ByteBuffer byteBuffer) { 19 | if (byteBuffer.isDirect() || byteBuffer.isReadOnly()) { 20 | byte[] bytes = new byte[byteBuffer.remaining()]; 21 | byteBuffer.get(bytes); 22 | return bytes; 23 | } 24 | else return byteBuffer.array(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/AbstractNetworkPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.io.IOException; 6 | import java.util.Map; 7 | 8 | public abstract class AbstractNetworkPacket { 9 | public abstract byte getID(); 10 | public abstract byte getVer(); 11 | 12 | public byte getVer(Map versions) { 13 | if (!versions.containsKey(this.getID())) throw new IllegalArgumentException("Versions should contain it's id"); 14 | return (byte) Math.min(this.getVer(), versions.get(this.getID())); 15 | } 16 | 17 | public abstract void read(ByteBuf byteBuf, NetData config, byte version) throws IOException; 18 | public abstract void write(ByteBuf byteBuf, NetData config, byte version) throws IOException; 19 | 20 | public abstract boolean doWrite(NetData config); 21 | 22 | public boolean isOptional() { 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/api/events/client/ClientNetworkEvents.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.api.events.client; 2 | 3 | import com.zigythebird.playeranimcore.event.Event; 4 | import io.github.kosmx.emotes.common.network.EmotePacket; 5 | 6 | /** 7 | * Network-related events on the client 8 | * Can be used for compatibility with replaymod or flashback 9 | */ 10 | public class ClientNetworkEvents { 11 | /** 12 | * Used to manipulate the packet before sending it 13 | */ 14 | public static final Event PACKET_SEND = new Event<>(listeners -> packet -> { 15 | for (PacketSendEvent listener : listeners) { 16 | listener.onPacketSend(packet); 17 | } 18 | }); 19 | 20 | @FunctionalInterface 21 | public interface PacketSendEvent { 22 | /** 23 | * Used to manipulate the packet before sending it 24 | * @param packet Emote packet 25 | */ 26 | void onPacketSend(EmotePacket.Builder packet); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | maven-central: 4 | type: maven-repository 5 | url: "https://repo.maven.apache.org/maven2/" 6 | redlance-repo: 7 | type: maven-repository 8 | url: "https://repo.redlance.org/public/" 9 | neoforged-maven: 10 | type: maven-repository 11 | url: "https://maven.neoforged.net/releases/" 12 | architectury-maven: 13 | type: maven-repository 14 | url: "https://maven.architectury.dev/" 15 | blamejared-maven: 16 | type: maven-repository 17 | url: "https://maven.blamejared.com/" 18 | updates: 19 | - package-ecosystem: github-actions 20 | directory: "/" 21 | schedule: 22 | interval: daily 23 | - package-ecosystem: gradle 24 | directory: "/" 25 | schedule: 26 | interval: daily 27 | registries: 28 | - maven-central 29 | - redlance-repo 30 | - neoforged-maven 31 | - architectury-maven 32 | - blamejared-maven 33 | ignore: 34 | - dependency-name: com.google.code.gson:gson 35 | - dependency-name: org.jetbrains:annotations 36 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/fi_FI.json: -------------------------------------------------------------------------------- 1 | { 2 | "emotecraft.menu": "Emoten asetukset", 3 | "emotecraft.options.options": "Modin asetukset", 4 | "emotecraft.options.keybind": "Aseta näppäin joka soittaa valitun emoten.", 5 | "emotecraft.options.fastmenu": "Valitse emote ja napsauta sitten", 6 | "emotecraft.options.fastmenu2": "emote-pyötää jotta voit lisätä sen.", 7 | "emotecraft.options.fastmenu3": "Napsauta hiiren oikealla painikkeella pyörää tyhjentääksesi sen.", 8 | "emotecraft.sure": "Korvaa näppäin", 9 | "emotecraft.sure2": "Toisella emotella on jo tämä näppäin. Haluatko korvata sen emoten tällä?", 10 | "emotecraft.otherconfig.category.general": "Perusasetukset", 11 | "emotecraft.otherconfig.category.expert": "Asiantuntijoiden Asetukset", 12 | "emotecraft.otherconfig": "Muut valinnat", 13 | "emotecraft.otherconfig.debug": "Vianmääritys", 14 | "emotecraft.otherconfig.enableNSFW": "Salli NSFW Emotet", 15 | "emotecraft.emotelist": "Kaikki Emotet", 16 | "key.emotecraft.fastchoose": "Avaa Emote-pyörä" 17 | } 18 | -------------------------------------------------------------------------------- /emotesAssets/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | } 4 | 5 | version = mod_version 6 | 7 | // Rename every file to lowercase. This is essential for the translations to work 8 | // Possibly creates other problems on other operating systems 9 | tasks.processResources { 10 | filesMatching("assets/emotecraft/lang/*.json") { 11 | val segments = relativePath.segments 12 | val newSegments = segments.dropLast(1) + segments.last().lowercase() 13 | relativePath = RelativePath(true, *newSegments.toTypedArray()) 14 | } 15 | } 16 | 17 | java { 18 | withSourcesJar() 19 | } 20 | 21 | publishing { 22 | publications { 23 | register("mavenJava") { 24 | artifactId = "emotesAssets" 25 | from(components["java"]) 26 | withCustomPom("emotesAssets", "Minecraft Emotecraft Assets") 27 | } 28 | } 29 | repositories { 30 | if (shouldPublishMaven) { 31 | kosmxRepo(project) 32 | } else { 33 | mavenLocal() 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/ConnectionHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import io.github.kosmx.emotes.arch.network.EmotesMixinConnection; 4 | import net.minecraft.network.Connection; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | @Mixin(Connection.class) 14 | public class ConnectionHandlerMixin implements EmotesMixinConnection { 15 | @Unique 16 | @NotNull 17 | private final HashMap emotecraft$versions = new HashMap<>(); 18 | 19 | @Override 20 | public @NotNull Map emotecraft$getRemoteVersions() { 21 | return this.emotecraft$versions; 22 | } 23 | 24 | @Override 25 | public void emotecraft$setVersions(@Nullable Map map) { 26 | this.emotecraft$versions.clear(); 27 | if (map != null) this.emotecraft$versions.putAll(map); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /minecraft/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import net.fabricmc.loom.api.LoomGradleExtensionAPI 4 | 5 | plugins { 6 | id("architectury-plugin") 7 | } 8 | 9 | architectury { 10 | minecraft = minecraft_version 11 | } 12 | 13 | version = mod_version 14 | 15 | subprojects { 16 | apply(plugin = "dev.architectury.loom") 17 | apply(plugin = "architectury-plugin") 18 | apply(plugin = "maven-publish") 19 | apply(plugin = "me.modmuss50.mod-publish-plugin") 20 | 21 | base.archivesName = "${archives_base_name}-${name}-for-MC${minecraft_version}" 22 | version = mod_version 23 | 24 | val loom = extensions.getByType(LoomGradleExtensionAPI::class) 25 | 26 | loom.silentMojangMappingsLicense() 27 | 28 | dependencies { 29 | configurations.getByName("minecraft")("com.mojang:minecraft:${minecraft_version}") 30 | configurations.getByName("mappings")(loom.layered { 31 | officialMojangMappings() 32 | parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip") 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/LivingEntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import io.github.kosmx.emotes.arch.screen.utils.UnsafeMannequin; 4 | import net.minecraft.client.renderer.entity.LivingEntityRenderer; 5 | import net.minecraft.world.entity.LivingEntity; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | 11 | @Mixin(LivingEntityRenderer.class) 12 | public class LivingEntityRendererMixin { 13 | @Inject( 14 | method = "shouldShowName(Lnet/minecraft/world/entity/LivingEntity;D)Z", 15 | at = @At( 16 | value = "HEAD" 17 | ), 18 | cancellable = true 19 | ) 20 | private void emotecraft$shouldShowName(LivingEntity livingEntity, double d, CallbackInfoReturnable cir) { 21 | if (livingEntity instanceof UnsafeMannequin) cir.setReturnValue(false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/StopPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import com.zigythebird.playeranimcore.network.NetworkUtils; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | public class StopPacket extends AbstractNetworkPacket { 8 | @Override 9 | public byte getID() { 10 | return PacketConfig.STOP_PACKET; 11 | } 12 | 13 | @Override 14 | public byte getVer() { 15 | return 1; 16 | } 17 | 18 | @Override 19 | public void read(ByteBuf buf, NetData config, byte version) { 20 | config.stopEmoteID = NetworkUtils.readUuid(buf); 21 | } 22 | 23 | @Override 24 | public void write(ByteBuf buf, NetData config, byte version) { 25 | assert config.stopEmoteID != null; 26 | NetworkUtils.writeUuid(buf, config.stopEmoteID); 27 | } 28 | 29 | @Override 30 | public boolean doWrite(NetData config) { 31 | return config.stopEmoteID != null; // Write only if config has true stop value 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/tools/UUIDMap.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.tools; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.UUID; 7 | import java.util.function.Predicate; 8 | import java.util.function.Supplier; 9 | 10 | //HashMap but with making my life easier 11 | public class UUIDMap> extends HashMap implements Iterable { 12 | public T put(T v){ 13 | return this.put(v.get(), v); 14 | } 15 | 16 | public void addAll(Collection m) { 17 | for(T t : m) { 18 | this.put(t); 19 | } 20 | } 21 | 22 | 23 | @Override 24 | public Iterator iterator() { 25 | return this.values().iterator(); 26 | } 27 | 28 | public void add(T value) { 29 | this.put(value); 30 | } 31 | 32 | public boolean contains(T value) { 33 | return this.containsKey(value.get()); 34 | } 35 | 36 | public void removeIf(Predicate predicate) { 37 | this.values().removeIf(predicate); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/gui/widgets/search/ISearchEngine.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.gui.widgets.search; 2 | 3 | import io.github.kosmx.emotes.PlatformTools; 4 | import io.github.kosmx.emotes.arch.gui.widgets.EmoteListWidget; 5 | import net.minecraft.client.gui.Font; 6 | import net.minecraft.client.gui.components.EditBox; 7 | import net.minecraft.client.gui.components.Renderable; 8 | import net.minecraft.network.chat.Component; 9 | 10 | import java.util.List; 11 | import java.util.function.Supplier; 12 | import java.util.stream.Stream; 13 | 14 | public interface ISearchEngine extends Renderable { 15 | EditBox createEditBox(Font font, Component message, Supplier> entries); 16 | boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY); 17 | 18 | Stream filter(Stream entries, String search); 19 | 20 | static ISearchEngine getInstance() { 21 | return PlatformTools.HAS_SEARCHABLES ? new SearchablesSearch() : VanillaSearch.INSTANCE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/widget/preview/elemets/PlayerChooseSquareElement.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.widget.preview.elemets; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import io.github.kosmx.emotes.arch.screen.widget.AbstractFastChooseWidget; 5 | 6 | public class PlayerChooseSquareElement extends PlayerChooseElement { 7 | protected final int dx; 8 | protected final int dy; 9 | 10 | public PlayerChooseSquareElement(AbstractFastChooseWidget parent, GameProfile profile, int id, int dx, int dy) { 11 | super(parent, profile, id); 12 | this.dx = dx; 13 | this.dy = dy; 14 | } 15 | 16 | @Override 17 | protected void updateRectangle(float easedProgress) { 18 | int s = this.parent.globalPadding(); 19 | float distance = (s * 2.5f) * easedProgress; 20 | int iconX = Math.round(parent.getX() + parent.getWidth() / 2F + this.dx * distance) - s; 21 | int iconY = Math.round(parent.getY() + parent.getHeight() / 2F + this.dy * distance) - s; 22 | 23 | setRectangle(s * 2, s * 2, iconX, iconY); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/he_IL.json: -------------------------------------------------------------------------------- 1 | { 2 | "emotecraft.options.options": "הגדרות מוד", 3 | "emotecraft.otherconfig.category.general": "אפשרויות בסיסיות", 4 | "emotecraft.otherconfig": "אפשרויות אחרות", 5 | "emotecraft.otherconfig.hideWarning": "הסתר אזהרת \"אין שרת\"", 6 | "emotecraft.emotelist": "כל ההנפשות", 7 | "emotecraft.no_server": "לשרת לא מותקנת גירסה של Emotecraft. משתמשים אחרים לא יראו את ההנפשות שלך אם תשתמש בהם.", 8 | "emotecraft.different_server": "לשרת מותקנת גירסה אחרת של Emotecraft. ייתכן ששחקנים אחרים לא יראו את ההנפשות שלך פועלים כראוי.", 9 | "key.emotecraft.stop": "Stop Playing Emote", 10 | "key.category.emotecraft.keybinding": "Emotecraft", 11 | "emotecraft.emote.author": "מחבר: ", 12 | "emotecraft.config": "הגדרות הנפשה", 13 | "emotecraft.blockedEmote": "אסור לך להשתמש בהנפשה הזאת.", 14 | "emotecraft.resetConfig.title": "איפוס הגדרות Emotecraft", 15 | "emotecraft.resetConfig.message": "Do you really want to reset your Emotecraft settings?", 16 | "emotecraft.options.export": "תפריט ייצוא", 17 | "emotecraft.song_too_big_to_send": "השיר גדול מכדי להישלח לשרת. שולח הנפשה בלבד..." 18 | } 19 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/widget/preview/elemets/PlayerChooseCircleElement.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.widget.preview.elemets; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import io.github.kosmx.emotes.arch.screen.widget.AbstractFastChooseWidget; 5 | 6 | public class PlayerChooseCircleElement extends PlayerChooseElement { 7 | protected final float angle; 8 | 9 | public PlayerChooseCircleElement(AbstractFastChooseWidget parent, GameProfile profile, int id, float angle) { 10 | super(parent, profile, id); 11 | this.angle = angle; 12 | } 13 | 14 | @Override 15 | protected void updateRectangle(float easedProgress) { 16 | int s = this.parent.globalPadding(); 17 | int iconX = (int) (((float) (parent.getX() + parent.getWidth() / 2)) + parent.getWidth() * 0.36 * Math.sin(this.angle * 0.0174533) * easedProgress) - s; 18 | int iconY = (int) (((float) (parent.getY() + parent.getHeight() / 2)) + parent.getHeight() * 0.36 * Math.cos(this.angle * 0.0174533) * easedProgress) - s; 19 | setRectangle(s * 2, s * 2, iconX, iconY); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/EmotecraftClientFabricMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric; 2 | 3 | import io.github.kosmx.emotes.arch.ClientCommands; 4 | import io.github.kosmx.emotes.arch.EmotecraftClientMod; 5 | import io.github.kosmx.emotes.fabric.network.ClientNetworkInstance; 6 | import net.fabricmc.api.ClientModInitializer; 7 | import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; 8 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; 9 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; 10 | 11 | public class EmotecraftClientFabricMod extends EmotecraftClientMod implements ClientModInitializer { 12 | @Override 13 | public void onInitializeClient() { 14 | KeyBindingHelper.registerKeyBinding(OPEN_MENU_KEY); 15 | KeyBindingHelper.registerKeyBinding(STOP_EMOTE_KEY); 16 | 17 | super.onInitializeClient(); 18 | ClientNetworkInstance.init(); //init network 19 | 20 | ClientTickEvents.END_CLIENT_TICK.register(this::onClientTick); 21 | ClientCommandRegistrationCallback.EVENT.register(ClientCommands::register); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /emotesAPI/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | `maven-publish` 5 | } 6 | 7 | version = mod_version 8 | 9 | dependencies { 10 | api("com.zigythebird.playeranim:PlayerAnimationLibCore:${project["playeranimlib_version"]}") 11 | api("net.raphimc:NoteBlockLib:${project["noteblocklib_version"]}") 12 | 13 | testImplementation("org.junit.jupiter:junit-jupiter-api:6.0.1") 14 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1") 15 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1") 16 | } 17 | 18 | tasks.test { 19 | useJUnitPlatform() 20 | } 21 | 22 | //-------- publishing -------- 23 | 24 | java { 25 | withSourcesJar() 26 | withJavadocJar() 27 | } 28 | 29 | publishing { 30 | publications { 31 | register("mavenJava") { 32 | artifactId = "emotesAPI" 33 | 34 | from(components["java"]) // jar, sourcesJar, javadocJar 35 | 36 | withCustomPom("emotesApi", "Minecraft Emotecraft API") 37 | } 38 | } 39 | 40 | repositories { 41 | if (shouldPublishMaven) { 42 | kosmxRepo(project) 43 | } else { 44 | mavenLocal() 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/PlayerDataPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import com.zigythebird.playeranimcore.network.NetworkUtils; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | public class PlayerDataPacket extends AbstractNetworkPacket{ 8 | @Override 9 | public byte getID() { 10 | return PacketConfig.PLAYER_DATA_PACKET; 11 | } 12 | 13 | @Override 14 | public byte getVer() { 15 | return 1; 16 | } 17 | 18 | @Override 19 | public void read(ByteBuf byteBuf, NetData config, byte version) { 20 | config.player = NetworkUtils.readUuid(byteBuf); 21 | if (version >= 1) config.isForced = byteBuf.readByte() != 0x00; 22 | } 23 | 24 | @Override 25 | public void write(ByteBuf byteBuf, NetData config, byte version) { 26 | assert config.player != null; 27 | 28 | NetworkUtils.writeUuid(byteBuf, config.player); 29 | if (version >= 1) byteBuf.writeByte(config.isForced ? (byte) 0x01 : (byte) 0x00); 30 | } 31 | 32 | @Override 33 | public boolean doWrite(NetData config) { 34 | return config.player != null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/neoforge/EmotecraftNeoMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.neoforge; 2 | 3 | import io.github.kosmx.emotes.mc.ServerCommands; 4 | import io.github.kosmx.emotes.common.CommonData; 5 | import io.github.kosmx.emotes.main.EmotecraftMod; 6 | import net.neoforged.api.distmarker.Dist; 7 | import net.neoforged.bus.api.SubscribeEvent; 8 | import net.neoforged.fml.common.Mod; 9 | import net.neoforged.neoforge.common.NeoForge; 10 | import net.neoforged.neoforge.event.RegisterCommandsEvent; 11 | import net.neoforged.neoforge.event.entity.player.PlayerEvent; 12 | 13 | @Mod(CommonData.MOD_ID) 14 | public class EmotecraftNeoMod extends EmotecraftMod { 15 | public EmotecraftNeoMod(Dist dist) { 16 | super.onInitialize(dist.isClient()); 17 | 18 | NeoForge.EVENT_BUS.register(this); 19 | } 20 | 21 | @SubscribeEvent 22 | public void onRegisterCommands(RegisterCommandsEvent event) { 23 | ServerCommands.register(event.getDispatcher(), event.getBuildContext(), event.getCommandSelection()); 24 | } 25 | 26 | @SubscribeEvent 27 | public void onStartTracking(PlayerEvent.StartTracking event) { 28 | super.onStartTracking(event.getTarget(), event.getEntity()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/network/PayloadTypeRegistator.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric.network; 2 | 3 | import io.github.kosmx.emotes.arch.network.EmotePacketPayload; 4 | import io.github.kosmx.emotes.arch.network.NetworkPlatformTools; 5 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 6 | import net.minecraft.network.FriendlyByteBuf; 7 | import net.minecraft.network.codec.StreamCodec; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | 10 | public class PayloadTypeRegistator { 11 | public static void init() { 12 | register(NetworkPlatformTools.EMOTE_CHANNEL_ID, EmotePacketPayload.EMOTE_CHANNEL_READER); 13 | register(NetworkPlatformTools.STREAM_CHANNEL_ID, EmotePacketPayload.STREAM_CHANNEL_READER); 14 | } 15 | 16 | private static void register(CustomPacketPayload.Type type, StreamCodec codec) { 17 | PayloadTypeRegistry.configurationS2C().register(type, codec); 18 | PayloadTypeRegistry.configurationC2S().register(type, codec); 19 | 20 | PayloadTypeRegistry.playS2C().register(type, codec); 21 | PayloadTypeRegistry.playC2S().register(type, codec); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/crowdin.yml: -------------------------------------------------------------------------------- 1 | name: Crowdin Action 2 | 3 | on: 4 | schedule: 5 | - cron: '42 0 * * *' 6 | workflow_dispatch: 7 | 8 | 9 | jobs: 10 | synchronize-with-crowdin: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | max-parallel: 1 15 | matrix: 16 | branch: [dev, 1.21.9] 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v6 21 | with: 22 | ref: ${{ matrix.branch }} 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: crowdin action 26 | uses: crowdin/github-action@v2 27 | with: 28 | config: "crowdin.yml" 29 | upload_sources: ${{ matrix.branch == 'dev' }} 30 | upload_translations: ${{ matrix.branch == 'dev' }} 31 | download_translations: true 32 | 33 | crowdin_branch_name: "dev" 34 | localization_branch_name: l10n_${{ matrix.branch }} 35 | pull_request_base_branch_name: ${{ matrix.branch }} 36 | skip_ref_checkout: true 37 | 38 | pull_request_title: 'New Crowdin translations for ${{ matrix.branch }}' 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | CROWDIN_PROJECT_ID: 414868 42 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} 43 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/emotePlay/instances/EmotecraftSoundEvents.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.emotePlay.instances; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import net.minecraft.client.resources.sounds.Sound; 5 | import net.minecraft.client.sounds.SoundEngine; 6 | import net.minecraft.client.sounds.WeighedSoundEvents; 7 | import net.minecraft.client.sounds.Weighted; 8 | import net.minecraft.util.RandomSource; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class EmotecraftSoundEvents extends WeighedSoundEvents { 12 | protected final Sound sound; 13 | 14 | public EmotecraftSoundEvents(Sound sound) { 15 | super(sound.getLocation(), CommonData.MOD_NAME); 16 | this.sound = sound; 17 | } 18 | 19 | @Override 20 | public @NotNull Sound getSound(RandomSource randomSource) { 21 | return this.sound; 22 | } 23 | 24 | @Override 25 | public int getWeight() { 26 | return this.sound.getWeight(); 27 | } 28 | 29 | @Override 30 | public void addSound(Weighted accessor) { 31 | // no-op 32 | } 33 | 34 | @Override 35 | public void preloadIfRequired(SoundEngine engine) { 36 | // no-op 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/emotePlay/instances/SoundEventInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.emotePlay.instances; 2 | 3 | import net.minecraft.client.resources.sounds.AbstractSoundInstance; 4 | import net.minecraft.client.sounds.SoundManager; 5 | import net.minecraft.client.sounds.WeighedSoundEvents; 6 | import net.minecraft.sounds.SoundEvent; 7 | import net.minecraft.sounds.SoundSource; 8 | import net.minecraft.util.RandomSource; 9 | import net.minecraft.world.phys.Vec3; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class SoundEventInstance extends AbstractSoundInstance { 13 | @SuppressWarnings("deprecation") 14 | private static final RandomSource RANDOM_SOURCE = RandomSource.createThreadSafe(); 15 | 16 | public SoundEventInstance(SoundEvent soundEvent, float volume, float pitch, Vec3 pos) { 17 | super(soundEvent, SoundSource.PLAYERS, RANDOM_SOURCE); 18 | this.volume = volume; 19 | this.pitch = pitch; 20 | this.x = pos.x(); 21 | this.y = pos.y(); 22 | this.z = pos.z(); 23 | } 24 | 25 | @Override 26 | public @NotNull WeighedSoundEvents resolve(SoundManager manager) { 27 | super.resolve(manager); 28 | return new EmotecraftSoundEvents(this.sound); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/AvatarAnimManagerMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import com.zigythebird.playeranim.animation.AvatarAnimManager; 6 | import io.github.kosmx.emotes.arch.screen.utils.UnsafeMannequin; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.world.entity.Avatar; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | 14 | @Mixin(AvatarAnimManager.class) 15 | public class AvatarAnimManagerMixin { 16 | @Shadow 17 | @Final 18 | private Avatar avatar; 19 | 20 | @WrapOperation( 21 | method = "handleAnimations", 22 | at = @At( 23 | value = "INVOKE", 24 | target = "Lnet/minecraft/client/Minecraft;isPaused()Z" 25 | ) 26 | ) 27 | private boolean emotecraft$unpause(Minecraft instance, Operation original) { 28 | if (this.avatar instanceof UnsafeMannequin) return false; 29 | return original.call(instance); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/EntityRenderDispatcherMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import io.github.kosmx.emotes.arch.screen.utils.UnsafeMannequin; 5 | import net.minecraft.client.Camera; 6 | import net.minecraft.client.renderer.entity.EntityRenderDispatcher; 7 | import net.minecraft.world.entity.Entity; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(EntityRenderDispatcher.class) 15 | public class EntityRenderDispatcherMixin { 16 | @Shadow 17 | public Camera camera; 18 | 19 | @Inject( 20 | method = { 21 | "distanceToSqr" 22 | }, 23 | at = @At( 24 | value = "HEAD" 25 | ), 26 | cancellable = true 27 | ) 28 | private void emotecraft$fixNPE(CallbackInfoReturnable cir, @Local(argsOnly = true) Entity entity) { 29 | if (this.camera == null || entity instanceof UnsafeMannequin) cir.setReturnValue(Double.MAX_VALUE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/EmotecraftFabricMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric; 2 | 3 | import io.github.kosmx.emotes.mc.ServerCommands; 4 | import io.github.kosmx.emotes.fabric.network.ServerNetworkStuff; 5 | import io.github.kosmx.emotes.main.EmotecraftMod; 6 | import net.fabricmc.api.EnvType; 7 | import net.fabricmc.api.ModInitializer; 8 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 9 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 10 | import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; 11 | import net.fabricmc.loader.api.FabricLoader; 12 | import net.minecraft.server.MinecraftServer; 13 | 14 | public class EmotecraftFabricMod extends EmotecraftMod implements ModInitializer { 15 | public static MinecraftServer SERVER_INSTANCE; 16 | 17 | @Override 18 | public void onInitialize() { 19 | super.onInitialize(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT); 20 | 21 | ServerNetworkStuff.init(); 22 | 23 | CommandRegistrationCallback.EVENT.register(ServerCommands::register); 24 | EntityTrackingEvents.START_TRACKING.register(this::onStartTracking); 25 | ServerLifecycleEvents.SERVER_STARTING.register(server -> SERVER_INSTANCE = server); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="*" 3 | license="GPL3" 4 | issueTrackerURL="https://github.com/KosmX/emotes/issues" 5 | 6 | [[mods]] 7 | modId="emotecraft" 8 | version="${version}" 9 | displayName="Emotecraft" 10 | updateJSONURL="https://api.modrinth.com/updates/emotecraft/forge_updates.json?neoforge=include" 11 | displayURL="https://docs.zigythebird.com/emotecraft/gettingstarted" 12 | logoFile="emotecraft_mod_logo.png" 13 | authors="KosmX, dima_dencep" 14 | credits="Kale Ko, ZigyTheBird, BoBkiNN" 15 | description='''${description}''' 16 | 17 | [[mixins]] 18 | config = "emotecraft-arch.mixins.json" 19 | 20 | [[dependencies.emotecraft]] 21 | modId = "neoforge" 22 | type = "required" 23 | versionRange = "[21.11.0-beta,)" 24 | ordering = "NONE" 25 | side = "BOTH" 26 | 27 | [[dependencies.emotecraft]] 28 | modId = "minecraft" 29 | type = "required" 30 | versionRange = "[1.21.11,)" 31 | ordering = "NONE" 32 | side = "BOTH" 33 | 34 | [[dependencies.emotecraft]] 35 | modId="player_animation_library" 36 | type="required" 37 | versionRange="[1.1.4+mc.1.21.11,)" 38 | ordering="NONE" 39 | side="BOTH" 40 | 41 | [[dependencies.emotecraft]] 42 | modId="bendable_cuboids" 43 | type="incompatible" 44 | versionRange="[1.0,1.0.6+mc.1.21.11)" 45 | ordering="NONE" 46 | side="BOTH" 47 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/NewAnimPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import com.zigythebird.playeranimcore.network.AnimationBinary; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | public class NewAnimPacket extends AbstractNetworkPacket { 8 | @Override 9 | public byte getID() { 10 | return PacketConfig.NEW_ANIMATION_FORMAT; 11 | } 12 | 13 | @Override 14 | public byte getVer() { 15 | return AnimationBinary.CURRENT_VERSION; 16 | } 17 | 18 | @Override 19 | public void read(ByteBuf buf, NetData config, byte version) { 20 | config.tick = buf.readFloat(); 21 | config.emoteData = AnimationBinary.read(buf, version); 22 | config.valid = true; // TODO 23 | } 24 | 25 | @Override 26 | public void write(ByteBuf buf, NetData config, byte version) { 27 | assert config.emoteData != null; 28 | 29 | buf.writeFloat(config.tick); 30 | AnimationBinary.write(buf, version, config.emoteData); 31 | } 32 | 33 | @Override 34 | public boolean doWrite(NetData data) { 35 | return data.emoteData != null && data.stopEmoteID == null && data.versions.containsKey(PacketConfig.NEW_ANIMATION_FORMAT); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/SoundEngineMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import com.llamalad7.mixinextras.sugar.Local; 6 | import io.github.kosmx.emotes.main.emotePlay.instances.SoundDirectInstance; 7 | import io.github.kosmx.emotes.main.emotePlay.instances.SoundEventInstance; 8 | import net.minecraft.client.resources.sounds.SoundInstance; 9 | import net.minecraft.client.sounds.SoundEngine; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | 13 | @Mixin(SoundEngine.class) 14 | public class SoundEngineMixin { 15 | @WrapOperation( 16 | method = "calculatePitch", 17 | at = @At( 18 | value = "INVOKE", 19 | target = "Lnet/minecraft/util/Mth;clamp(FFF)F" 20 | ) 21 | ) 22 | private float emotecraft$extendOctaves(float value, float min, float max, Operation original, @Local(argsOnly = true) SoundInstance sound) { 23 | if (sound instanceof SoundEventInstance || sound instanceof SoundDirectInstance) { 24 | return value; 25 | } 26 | return original.call(value, min, max); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs = -Xmx4G 2 | org.gradle.parallel=true 3 | loom.ignoreDependencyLoomVersionValidation=true 4 | 5 | # Publishing 6 | modrinth_id = pZ2wrerK 7 | curseforge_id_forge = 403422 8 | curseforge_slug_forge = emotecraft-forge 9 | curseforge_id_fabric = 397809 10 | curseforge_slug_fabric = emotecraft 11 | hangar_id = emotecraft 12 | minecraft_release_versions = 1.21.11 13 | 14 | # Properties 15 | minecraft_version = 1.21.11 16 | parchment_version = 2025.12.20 17 | fabric_loader_version = 0.18.3 18 | neoforge_version = 21.11.13-beta 19 | 20 | # Mod Properties 21 | mod_description = Play emotes in Minecraft\nOpen the menu to setup your fast-choose wheel\n\nYou can add custom emotes, and you can play these even if nobody else has them!\n\nSpecial thanks:\n- Kale Ko for creating emotes and help in the UI design\n\nMaintained by dima_dencep and ZigyTheBird 22 | version_base = 3.2.0 23 | archives_base_name = emotecraft 24 | 25 | # Dependencies 26 | java_version = 21 27 | translationfallbacks_version = 1.1.0+mc1.21.8 28 | playeranimlib_version = 1.1.4+mc.1.21.11 29 | bendablecuboids_version = 1.0.6+mc.1.21.9 30 | modmenu_version = 17.0.0-beta.1 31 | fabric_api_version = 0.140.2+1.21.11 32 | searchables_version = 1.0.2 33 | fabric_permissions_api = 0.6.1 34 | noteblocklib_version = 3.1.1 35 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/ServerPlayNetworkMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import io.github.kosmx.emotes.arch.network.EmotesMixinNetwork; 4 | import io.github.kosmx.emotes.arch.network.ModdedServerPlayNetwork; 5 | import net.minecraft.network.Connection; 6 | import net.minecraft.server.MinecraftServer; 7 | import net.minecraft.server.network.CommonListenerCookie; 8 | import net.minecraft.server.network.ServerCommonPacketListenerImpl; 9 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | 14 | @Mixin(ServerGamePacketListenerImpl.class) 15 | public abstract class ServerPlayNetworkMixin extends ServerCommonPacketListenerImpl implements EmotesMixinNetwork { 16 | @Unique 17 | private final ModdedServerPlayNetwork emotecraft$instance = new ModdedServerPlayNetwork((ServerGamePacketListenerImpl)(Object) this); 18 | 19 | public ServerPlayNetworkMixin(MinecraftServer minecraftServer, Connection connection, CommonListenerCookie commonListenerCookie) { 20 | super(minecraftServer, connection, commonListenerCookie); 21 | } 22 | 23 | @Override 24 | public @NotNull ModdedServerPlayNetwork emotecraft$getServerNetworkInstance() { 25 | return this.emotecraft$instance; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/PacketTask.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network; 2 | 3 | 4 | public enum PacketTask { 5 | UNKNOWN(0, false, false, false), 6 | STREAM(1, true, false, true), 7 | CONFIG(8, false, false, false), 8 | STOP(10, true, false, true), 9 | FILE(0x10, true, true, false); 10 | 11 | public final byte id; 12 | 13 | /** 14 | * True if task is player emote play related 15 | */ 16 | public final boolean isEmoteStream; 17 | /** 18 | * Exchange name author and desc data 19 | */ 20 | public final boolean exchangeHeader; 21 | 22 | /** 23 | * It has to do something with a specific player 24 | */ 25 | public final boolean playerBound; 26 | 27 | PacketTask(byte id, boolean isEmoteStream, boolean exchangeHeader, boolean playerBound) { 28 | this.id = id; 29 | this.isEmoteStream = isEmoteStream; 30 | this.exchangeHeader = exchangeHeader; 31 | this.playerBound = playerBound; 32 | } 33 | public static PacketTask getTaskFromID(byte b) { 34 | for(PacketTask task:PacketTask.values()){ 35 | if(task.id == b)return task; 36 | } 37 | return UNKNOWN; 38 | } 39 | 40 | PacketTask(int i, boolean isEmoteStream, boolean exchangeHeader, boolean playerBound) { 41 | this((byte) i, isEmoteStream, exchangeHeader, playerBound); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/AvatarServerPlayNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.common.network.EmotePacket; 4 | import net.minecraft.world.entity.Avatar; 5 | import net.minecraft.world.entity.player.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.nio.ByteBuffer; 9 | 10 | /** 11 | * Wrapper class for avatars 12 | */ 13 | public final class AvatarServerPlayNetwork extends AbstractServerNetwork { 14 | @NotNull 15 | private final Avatar avatar; 16 | 17 | public AvatarServerPlayNetwork(@NotNull Avatar avatar) { 18 | super(); 19 | 20 | if (avatar instanceof Player) throw new UnsupportedOperationException("For players, use ModdedServerPlayNetwork!"); 21 | this.avatar = avatar; 22 | } 23 | 24 | @Override 25 | protected @NotNull EmotesMixinConnection getServerConnection() { 26 | throw new UnsupportedOperationException("Only players can have a connection!"); 27 | } 28 | 29 | @Override 30 | protected @NotNull Avatar getAvatar() { 31 | return this.avatar; 32 | } 33 | 34 | @Override 35 | public boolean isActive() { 36 | return false; 37 | } 38 | 39 | @Override 40 | public void sendPlayMessage(EmotePacket bytes) { 41 | throw new UnsupportedOperationException("Only players can have a connection!"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/ConfigTask.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import io.github.kosmx.emotes.common.network.EmotePacket; 5 | import io.github.kosmx.emotes.common.network.PacketConfig; 6 | import io.github.kosmx.emotes.common.network.objects.NetData; 7 | import net.minecraft.network.protocol.Packet; 8 | import net.minecraft.server.network.ConfigurationTask; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.function.Consumer; 12 | 13 | public class ConfigTask implements ConfigurationTask { 14 | public static final ConfigurationTask.Type TYPE = new Type("emotes:config"); 15 | 16 | @Override 17 | public void start(@NotNull Consumer> consumer) { 18 | NetData configData = new EmotePacket.Builder().configureToConfigExchange().build().data; 19 | configData.versions.put(PacketConfig.SERVER_TRACK_EMOTE_PLAY, (byte)0x01); // track player state 20 | try { 21 | EmotePacket packet = new EmotePacket.Builder(configData).build(); 22 | consumer.accept(NetworkPlatformTools.playPacket(packet)); // Config init 23 | } catch (Throwable e) { 24 | CommonData.LOGGER.warn("Failed to configure client!", e); 25 | } 26 | } 27 | 28 | @Override 29 | public @NotNull Type type() { 30 | return TYPE; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Build 5 | 6 | on: [pull_request, workflow_dispatch] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: checkout repository 13 | uses: actions/checkout@v6 14 | - name: Setup JDK 21 15 | uses: actions/setup-java@v5 16 | with: 17 | distribution: 'microsoft' 18 | java-version: 21 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@v5 21 | with: 22 | gradle-version: wrapper 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build All 26 | run: ./gradlew build 27 | 28 | - name: Copy artifacts 29 | run: | 30 | mkdir -p artifacts 31 | if [ -d "paper/build/libs" ]; then 32 | cp paper/build/libs/*.jar artifacts/ 33 | fi 34 | if [ -d "minecraft/fabric/build/libs" ]; then 35 | cp minecraft/fabric/build/libs/*.jar artifacts/ 36 | fi 37 | if [ -d "minecraft/neoforge/build/libs" ]; then 38 | cp minecraft/neoforge/build/libs/*.jar artifacts/ 39 | fi 40 | - uses: actions/upload-artifact@v6 41 | with: 42 | name: Build Results 43 | path: artifacts/*.jar 44 | -------------------------------------------------------------------------------- /emotesMc/src/main/java/io/github/kosmx/emotes/mc/services/IPermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.mc.services; 2 | 3 | import io.github.kosmx.emotes.api.services.IEmotecraftService; 4 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 5 | import io.github.kosmx.emotes.mc.services.impl.VanillaPermissionService; 6 | import net.minecraft.commands.CommandSourceStack; 7 | import net.minecraft.server.permissions.Permission; 8 | import net.minecraft.server.permissions.PermissionLevel; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | import java.util.function.Predicate; 14 | 15 | public interface IPermissionService extends IEmotecraftService { 16 | IPermissionService INSTANCE = ServiceLoaderUtil.loadService(IPermissionService.class, VanillaPermissionService::new); 17 | 18 | default Predicate require(@NotNull String permission, PermissionLevel defaultValue) { 19 | Objects.requireNonNull(permission, "permission"); 20 | return player -> check(player, permission, defaultValue); 21 | } 22 | 23 | default boolean check(@NotNull CommandSourceStack source, @NotNull String permission, PermissionLevel defaultValue) { 24 | return getPermissionValue(source, permission).orElseGet(() -> source.permissions().hasPermission(new Permission.HasCommandLevel(defaultValue))); 25 | } 26 | 27 | Optional getPermissionValue(@NotNull CommandSourceStack source, @NotNull String permission); 28 | } 29 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/mixin/PlayerMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.mixin; 2 | 3 | import io.github.kosmx.emotes.main.mixinFunctions.IPlayerEntity; 4 | import net.minecraft.world.entity.Avatar; 5 | import net.minecraft.world.entity.EntityType; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.entity.Pose; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.level.Level; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(Player.class) 17 | public abstract class PlayerMixin extends Avatar { 18 | @Shadow 19 | public abstract boolean isLocalPlayer(); 20 | 21 | protected PlayerMixin(EntityType entityType, Level level) { 22 | super(entityType, level); 23 | } 24 | 25 | @Inject(method = "updatePlayerPose", at = @At(value = "TAIL")) 26 | private void updatePlayerPoseEvent(CallbackInfo ci) { 27 | if (isLocalPlayer() && this instanceof IPlayerEntity animator) { 28 | if (getPose() == Pose.CROUCHING || getPose() == Pose.DYING || getPose() == Pose.SWIMMING || getPose() == Pose.FALL_FLYING || getPose() == Pose.SLEEPING) { 29 | animator.emotecraft$playerEntersInvalidPose(); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/impl/QuarkReaderWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type.impl; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import io.github.kosmx.emotes.server.config.Serializer; 5 | import io.github.kosmx.emotes.server.serializer.type.EmoteSerializerException; 6 | import io.github.kosmx.emotes.server.serializer.type.IReader; 7 | 8 | import java.io.InputStream; 9 | import java.util.Map; 10 | 11 | public class QuarkReaderWrapper implements IReader { 12 | @Override 13 | public Map read(InputStream stream, String filename) throws EmoteSerializerException { 14 | /*try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { 15 | QuarkReader quarkReader = new QuarkReader(); 16 | quarkReader.deserialize(reader, filename); 17 | 18 | return Collections.singletonList(quarkReader.getEmote()); 19 | } catch (Throwable th) { 20 | throw new EmoteSerializerException("Quark error", getExtension(), th); 21 | }*/ 22 | throw new EmoteSerializerException("Quark error", getExtension(), new UnsupportedOperationException("quark is not supported")); 23 | } 24 | 25 | @Override 26 | public String getExtension() { 27 | return "emote"; 28 | } 29 | 30 | @Override 31 | public boolean isActive() { 32 | return IReader.super.isActive() && Serializer.getConfig().enableQuark.get(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/gui/widgets/search/VanillaSearch.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.gui.widgets.search; 2 | 3 | import io.github.kosmx.emotes.arch.gui.widgets.EmoteListWidget; 4 | import net.minecraft.client.gui.Font; 5 | import net.minecraft.client.gui.GuiGraphics; 6 | import net.minecraft.client.gui.components.Button; 7 | import net.minecraft.client.gui.components.EditBox; 8 | import net.minecraft.network.chat.Component; 9 | 10 | import java.util.List; 11 | import java.util.function.Supplier; 12 | import java.util.stream.Stream; 13 | 14 | public class VanillaSearch implements ISearchEngine { 15 | public static final VanillaSearch INSTANCE = new VanillaSearch(); 16 | 17 | protected VanillaSearch() { 18 | } 19 | 20 | @Override 21 | public EditBox createEditBox(Font font, Component message, Supplier> entries) { 22 | return new EditBox(font, 0, 0, Button.BIG_WIDTH, Button.DEFAULT_HEIGHT, message); 23 | } 24 | 25 | @Override 26 | public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { 27 | return false; 28 | } 29 | 30 | @Override 31 | public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { 32 | // no-op 33 | } 34 | 35 | @Override 36 | public Stream filter(Stream entries, String search) { 37 | return entries.filter(entry -> entry.matches(search.toLowerCase())); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/ModdedServerPlayNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.arch.mixin.ServerCommonPacketListenerAccessor; 4 | import io.github.kosmx.emotes.common.network.EmotePacket; 5 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 6 | import net.minecraft.world.entity.Avatar; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * Wrapper class for Emotes play network implementation 11 | */ 12 | public final class ModdedServerPlayNetwork extends AbstractServerNetwork { 13 | @NotNull 14 | private final ServerGamePacketListenerImpl serverGamePacketListener; 15 | 16 | public ModdedServerPlayNetwork(@NotNull ServerGamePacketListenerImpl serverGamePacketListener) { 17 | super(); 18 | this.serverGamePacketListener = serverGamePacketListener; 19 | } 20 | 21 | @Override 22 | protected @NotNull EmotesMixinConnection getServerConnection() { 23 | return (EmotesMixinConnection) ((ServerCommonPacketListenerAccessor)serverGamePacketListener).getConnection(); 24 | } 25 | 26 | @Override 27 | protected @NotNull Avatar getAvatar() { 28 | return this.serverGamePacketListener.player; 29 | } 30 | 31 | @Override 32 | public boolean isActive() { 33 | return true; // TODO 34 | } 35 | 36 | @Override 37 | public void sendPlayMessage(EmotePacket packet) { 38 | this.serverGamePacketListener.send(NetworkPlatformTools.playPacket(packet)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/arch/network/fabric/NetworkPlatformToolsImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network.fabric; 2 | 3 | import io.github.kosmx.emotes.arch.network.NetworkPlatformTools; 4 | import io.github.kosmx.emotes.fabric.EmotecraftFabricMod; 5 | import net.fabricmc.fabric.api.networking.v1.PlayerLookup; 6 | import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; 7 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 8 | import net.minecraft.resources.Identifier; 9 | import net.minecraft.server.MinecraftServer; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; 12 | import net.minecraft.world.entity.Entity; 13 | 14 | import java.util.Collection; 15 | 16 | public final class NetworkPlatformToolsImpl implements NetworkPlatformTools { 17 | @Override 18 | public boolean canSendPlay(ServerPlayer player, Identifier channel) { 19 | return ServerPlayNetworking.canSend(player, channel); 20 | } 21 | 22 | @Override 23 | public boolean canSendConfig(ServerConfigurationPacketListenerImpl player, Identifier channel) { 24 | return ServerConfigurationNetworking.canSend(player, channel); 25 | } 26 | 27 | @Override 28 | public Collection getTrackedBy(Entity entity) { 29 | return PlayerLookup.tracking(entity); 30 | } 31 | 32 | @Override 33 | public MinecraftServer getServer() { 34 | return EmotecraftFabricMod.SERVER_INSTANCE; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/PacketConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network; 2 | 3 | /** 4 | * Utility class 5 | *

6 | * Definitions for packet version map keys 7 | */ 8 | public final class PacketConfig { 9 | /** 10 | * Max animation version supported by the other side. 11 | * (playeranimator) 12 | */ 13 | public static final byte LEGACY_ANIMATION_FORMAT = (byte) 0; 14 | 15 | /** 16 | * Max animation version supported by the other side. 17 | * (playeranimlib) 18 | */ 19 | public static final byte NEW_ANIMATION_FORMAT = (byte) 0x99; 20 | 21 | /** 22 | * Enable/disable NBS on this server/client. 23 | */ 24 | public static final byte NBS_CONFIG = (byte) 3; 25 | 26 | /** 27 | * Announce emote play state tracking feature. Mod and bukkit plugin Emotecraft does track state on server. 28 | * If the server sets it to 0 (false) the client will repeat all emote play messages if a new player is seen. 29 | */ 30 | public static final byte SERVER_TRACK_EMOTE_PLAY = (byte) 0x80; 31 | 32 | /** 33 | * Whether the server allows huge emotes in play state. 34 | * 0: no 35 | * any non-zero value: yes 36 | */ 37 | // public static final byte ALLOW_EMOTE_STREAM = (byte) 0x81; 38 | 39 | public static final byte DISCOVERY_PACKET = (byte) 8; 40 | public static final byte HEADER_PACKET = 0x11; 41 | public static final byte ICON_PACKET = (byte) 0x12; 42 | public static final byte PLAYER_DATA_PACKET = (byte) 1; 43 | public static final byte STOP_PACKET = (byte) 10; 44 | } 45 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/services/InstanceService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.services; 2 | 3 | import io.github.kosmx.emotes.api.services.IEmotecraftService; 4 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 5 | import io.github.kosmx.emotes.server.config.Serializer; 6 | import io.github.kosmx.emotes.server.services.impl.InstanceServiceImpl; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | 12 | public interface InstanceService extends IEmotecraftService { 13 | InstanceService INSTANCE = ServiceLoaderUtil.loadService(InstanceService.class, InstanceServiceImpl::new); 14 | 15 | Path getGameDirectory(); 16 | 17 | default Path getExternalEmoteDir() { 18 | return getGameDirectory().resolve(Serializer.getConfig().emotesDir.get()); 19 | } 20 | 21 | default Path getConfigPath() { 22 | String directoryName = "config"; 23 | 24 | try { 25 | directoryName = System.getProperty("emotecraftConfigDir", "config"); 26 | if (directoryName.equals("pluginDefault")) { 27 | directoryName = "plugins/emotecraft"; 28 | } 29 | } catch(Throwable ignore) { 30 | } 31 | 32 | if (!Files.exists(getGameDirectory().resolve(directoryName))) { 33 | try { 34 | Files.createDirectories(getGameDirectory().resolve(directoryName)); 35 | } catch(IOException ignored) { 36 | } 37 | } 38 | return getGameDirectory().resolve(directoryName).resolve("emotecraft.json"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /minecraft/archCommon/build.gradle.kts: -------------------------------------------------------------------------------- 1 | loom { 2 | accessWidenerPath = file("src/main/resources/emotes.accesswidener") 3 | } 4 | 5 | dependencies { 6 | modCompileOnly("net.fabricmc:fabric-loader:${fabric_loader_version}") 7 | 8 | implementation(project(":emotesAssets")) 9 | implementation(project(":emotesAPI")) 10 | implementation(project(":emotesServer")) 11 | api(project(path = ":emotesMc", configuration = "namedElements")) 12 | 13 | modApi("com.zigythebird.playeranim:PlayerAnimationLibCommon:${project["playeranimlib_version"]}") 14 | implementation("com.zigythebird.playeranim:PlayerAnimationLibCore:${project["playeranimlib_version"]}") 15 | 16 | // Third-party 17 | compileOnly("com.blamejared.searchables:Searchables-common-${minecraft_version}:${project["searchables_version"]}") { 18 | isTransitive = false 19 | } 20 | } 21 | 22 | java { 23 | withSourcesJar() 24 | } 25 | 26 | publishing { 27 | publications { 28 | register("mavenJava") { 29 | artifactId = "archCommon" 30 | 31 | artifact(tasks.jar) { 32 | classifier = "" 33 | } 34 | artifact(tasks.sourcesJar) 35 | 36 | addDeps(project, configurations.api.get(), "compile") 37 | addDeps(project, configurations.modApi.get(), "compile") 38 | 39 | withCustomPom("archCommon", "Minecraft Emotecraft Architectury common module") 40 | } 41 | } 42 | 43 | repositories { 44 | if (shouldPublishMaven) { 45 | kosmxRepo(project) 46 | } else { 47 | mavenLocal() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /emotesMc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | id("dev.architectury.loom") 5 | `maven-publish` 6 | } 7 | 8 | loom { 9 | silentMojangMappingsLicense() 10 | } 11 | 12 | version = mod_version 13 | 14 | dependencies { 15 | minecraft("com.mojang:minecraft:${minecraft_version}") 16 | mappings(loom.layered() { 17 | officialMojangMappings() 18 | parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip") 19 | }) 20 | 21 | api(project(":emotesServer")) { 22 | exclude(group = "org.jetbrains", module = "annotations") 23 | 24 | exclude(module = "gson") 25 | exclude(module = "slf4j-api") 26 | exclude(module = "fastutil") 27 | exclude(module = "guava") 28 | exclude(module = "netty-buffer") 29 | } 30 | } 31 | 32 | java { 33 | withSourcesJar() 34 | } 35 | 36 | tasks.jar { 37 | archiveClassifier = "" 38 | } 39 | 40 | tasks.remapJar { 41 | enabled = false 42 | } 43 | 44 | tasks.remapSourcesJar { 45 | enabled = false 46 | } 47 | 48 | publishing { 49 | publications { 50 | register("mavenJava") { 51 | artifactId = "emotesMc" 52 | 53 | artifact(tasks.jar) 54 | artifact(tasks.sourcesJar) 55 | 56 | addDeps(project, configurations.api, "compile") 57 | 58 | withCustomPom("emotesMc", "Emotecraft common serverside Minecraft code") 59 | } 60 | } 61 | 62 | repositories { 63 | if (shouldPublishMaven) { 64 | kosmxRepo(project) 65 | } else { 66 | mavenLocal() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/lang/ar_SA.json: -------------------------------------------------------------------------------- 1 | { 2 | "modmenu.descriptionTranslation.emotecraft": "لتشغيل إلايماءات في Minecraft\nافتح القائمة لتجهيز عجلة الاختيار السريع\n\nيمكنك إضافة إيماءات تعبيرية مخصصة، ويمكنك أن تستخدمها حتى لو لم يكن هناك من يملكها!\n\nشكر خاص الى:\n- Kale Ko لخلق إيماءة والمساعدة في تصميم واجهة المستخدم\n\nتم صناعته بواسطة dima_dencep و ZigyTheBird", 3 | "emotecraft.options.options": "خيارات المود", 4 | "emotecraft.options.keybind": "عيّن مفتاحًا مخصص لتشغيل الرموز التعبيرية المختارة", 5 | "emotecraft.options.fastmenu": "اختر رمزًا تعبيريًا عن طريق النقر عليه", 6 | "emotecraft.options.fastmenu2": "للإضافة إلى عجلة الإختيار السريع.", 7 | "emotecraft.options.fastmenu3": "نقر الزر الأيمن للمسح", 8 | "emotecraft.sure2": "رمزًا تعبيريًا آخر لديه هذا المفتاح.\nهل تريد اعادة ضبط ذلك وتعيين المفتاح لذلك؟", 9 | "emotecraft.otherconfig.category.general": "الخيارات الأساسية", 10 | "emotecraft.otherconfig.category.expert": "خيارات الخبراء", 11 | "emotecraft.otherconfig": "الخيارات الأخرى", 12 | "emotecraft.otherconfig.debug": "التصحيح", 13 | "emotecraft.otherconfig.validate": "التحقق من صحة الرمز التعبيري", 14 | "emotecraft.otherconfig.validate.tooltip": "فقط في عوالم PvP، حيث شخص ما يريد الغش. لن يسمح بالرموز التعبيرية، ماذا يصنع الكثير من التعويض", 15 | "emotecraft.otherconfig.showHiddenConfig.tooltip": ".", 16 | "key.category.emotecraft.keybinding": "Emotecraft", 17 | "emotecraft.emote.author": "المؤلف: ", 18 | "emotecraft.options.export": "قائمة التصدير", 19 | "emotecraft.export": "تصدير الإيماءات في .%s", 20 | "emotecraft.song_too_big_to_send": "الأغنية كبيرة جدًا، ولا يمكن إرسالها إلى الخادم. يُمكن إرسال الرموز التعبيرية فقط" 21 | } 22 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/EmotecraftMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main; 2 | 3 | import io.github.kosmx.emotes.arch.network.CommonServerNetworkHandler; 4 | import io.github.kosmx.emotes.common.SerializableConfig; 5 | import io.github.kosmx.emotes.main.config.ClientConfig; 6 | import io.github.kosmx.emotes.main.config.ClientConfigSerializer; 7 | import io.github.kosmx.emotes.server.config.ConfigSerializer; 8 | import io.github.kosmx.emotes.server.config.Serializer; 9 | import io.github.kosmx.emotes.server.serializer.UniversalEmoteSerializer; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.entity.player.Player; 13 | 14 | public abstract class EmotecraftMod { 15 | protected void onInitialize(boolean isClient) { 16 | if (isClient) { 17 | Serializer.INSTANCE = new Serializer<>(new ClientConfigSerializer(), ClientConfig.class); 18 | } else { 19 | Serializer.INSTANCE = new Serializer<>(new ConfigSerializer<>(SerializableConfig::new), SerializableConfig.class); 20 | UniversalEmoteSerializer.loadEmotes(); 21 | } 22 | } 23 | 24 | protected void onStartTracking(Entity entity, Player player) { 25 | if (entity instanceof ServerPlayer tracked && player instanceof ServerPlayer tracker) { 26 | CommonServerNetworkHandler.getInstance().playerStartTracking( 27 | CommonServerNetworkHandler.getInstance().getPlayerNetworkInstance(tracked), 28 | CommonServerNetworkHandler.getInstance().getPlayerNetworkInstance(tracker) 29 | ); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/EmotePacketPayload.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.common.network.EmotePacket; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public record EmotePacketPayload(@NotNull CustomPacketPayload.Type id, @NotNull EmotePacket packet) implements CustomPacketPayload { 10 | @Override 11 | public @NotNull Type type() { 12 | return id; 13 | } 14 | 15 | public static @NotNull CustomPacketPayload playPacket(@NotNull EmotePacket packet) { 16 | return new EmotePacketPayload(NetworkPlatformTools.EMOTE_CHANNEL_ID, packet); 17 | } 18 | 19 | public static @NotNull CustomPacketPayload streamPacket(@NotNull EmotePacket packet) { 20 | return new EmotePacketPayload(NetworkPlatformTools.STREAM_CHANNEL_ID, packet); 21 | } 22 | 23 | @NotNull 24 | public static StreamCodec reader(@NotNull CustomPacketPayload.Type channel) { 25 | return CustomPacketPayload.codec((payload, buf) -> payload.packet().write(buf, buf.alloc()), buf -> new EmotePacketPayload(channel, new EmotePacket(buf))); 26 | } 27 | 28 | public static final StreamCodec EMOTE_CHANNEL_READER = reader(NetworkPlatformTools.EMOTE_CHANNEL_ID); 29 | public static final StreamCodec STREAM_CHANNEL_READER = reader(NetworkPlatformTools.STREAM_CHANNEL_ID); 30 | } 31 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/EmoteIconPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import io.github.kosmx.emotes.common.network.PacketConfig; 4 | import io.github.kosmx.emotes.common.network.PacketTask; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | public class EmoteIconPacket extends AbstractNetworkPacket{ 10 | @Override 11 | public byte getID() { 12 | return PacketConfig.ICON_PACKET; 13 | } 14 | 15 | @Override 16 | public byte getVer() { 17 | return 0x12; 18 | } 19 | 20 | @Override 21 | public void read(ByteBuf byteBuf, NetData config, byte version) { 22 | int size = byteBuf.readInt(); 23 | if (size != 0) { 24 | byte[] bytes = new byte[size]; 25 | byteBuf.readBytes(bytes); 26 | config.extraData.put("iconData", ByteBuffer.wrap(bytes)); 27 | } 28 | } 29 | 30 | @Override 31 | public void write(ByteBuf byteBuf, NetData config, byte version) { 32 | assert config.emoteData != null; 33 | ByteBuffer iconData = (ByteBuffer)config.emoteData.data().getRaw("iconData"); 34 | 35 | try { 36 | byteBuf.writeInt(iconData.remaining()); 37 | byteBuf.writeBytes(iconData); 38 | } finally { 39 | iconData.position(0); 40 | } 41 | } 42 | 43 | @Override 44 | public boolean doWrite(NetData config) { 45 | return config.purpose == PacketTask.FILE && config.emoteData != null && config.emoteData.data().has("iconData"); 46 | } 47 | 48 | @Override 49 | public boolean isOptional() { 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/EmoteDataPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import com.zigythebird.playeranimcore.network.LegacyAnimationBinary; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * It should be placed into emotecraftCommon, but it has too many references to minecraft codes... 11 | */ 12 | public class EmoteDataPacket extends AbstractNetworkPacket { 13 | @Override 14 | public void write(ByteBuf buf, NetData config, byte version) { 15 | assert config.emoteData != null; 16 | buf.writeInt((int) config.tick); 17 | LegacyAnimationBinary.write(config.emoteData, buf, version); 18 | } 19 | 20 | @Override 21 | public void read(ByteBuf buf, NetData config, byte version) throws IOException { 22 | config.tick = buf.readInt(); 23 | config.emoteData = LegacyAnimationBinary.read(buf, version); 24 | config.valid = true; // TODO 25 | } 26 | 27 | @Override 28 | public byte getID() { 29 | return PacketConfig.LEGACY_ANIMATION_FORMAT; 30 | } 31 | 32 | /** 33 | * version 1: 2.1 features, extended parts, UUID emote ID 34 | * version 2: Animation library, dynamic parts 35 | * version 3: Animation scale 36 | * version 4: easing args 37 | */ 38 | @Override 39 | public byte getVer() { 40 | return (byte) LegacyAnimationBinary.getCurrentVersion(); 41 | } 42 | 43 | @Override 44 | public boolean doWrite(NetData data) { 45 | return data.emoteData != null && data.stopEmoteID == null && !data.versions.containsKey(PacketConfig.NEW_ANIMATION_FORMAT); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/arch/network/neoforge/NetworkPlatformToolsImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network.neoforge; 2 | 3 | import io.github.kosmx.emotes.arch.network.NetworkPlatformTools; 4 | import net.minecraft.resources.Identifier; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.server.level.ChunkMap; 7 | import net.minecraft.server.level.ServerLevel; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; 10 | import net.minecraft.server.network.ServerPlayerConnection; 11 | import net.minecraft.world.entity.Entity; 12 | import net.neoforged.neoforge.server.ServerLifecycleHooks; 13 | 14 | import java.util.Collection; 15 | import java.util.stream.Collectors; 16 | 17 | public final class NetworkPlatformToolsImpl implements NetworkPlatformTools { 18 | @Override 19 | public boolean canSendPlay(ServerPlayer player, Identifier channel) { 20 | return player.connection.hasChannel(channel); 21 | } 22 | 23 | @Override 24 | public boolean canSendConfig(ServerConfigurationPacketListenerImpl packetListener, Identifier channel) { 25 | return packetListener.hasChannel(channel); 26 | } 27 | 28 | @Override 29 | public Collection getTrackedBy(Entity entity) { 30 | ChunkMap.TrackedEntity tracked = ((ServerLevel) entity.level()).getChunkSource().chunkMap.entityMap.get(entity.getId()); 31 | return tracked.seenBy.stream() 32 | .map(ServerPlayerConnection::getPlayer) 33 | .collect(Collectors.toUnmodifiableSet()); 34 | } 35 | 36 | @Override 37 | public MinecraftServer getServer() { 38 | return ServerLifecycleHooks.getCurrentServer(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/DiscoveryPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.github.kosmx.emotes.common.network.PacketTask; 6 | import io.netty.buffer.ByteBuf; 7 | 8 | import java.util.HashMap; 9 | 10 | public class DiscoveryPacket extends AbstractNetworkPacket { 11 | @Override 12 | public void read(ByteBuf buf, NetData data, byte version) { 13 | // Read these into versions 14 | int size = buf.readInt(); 15 | HashMap map = new HashMap<>(); 16 | 17 | for(int i = 0; i < size; i++){ 18 | byte id = buf.readByte(); 19 | byte ver = buf.readByte(); 20 | map.put(id, ver); 21 | } 22 | 23 | // check if every is exists, if not, return false 24 | // That is done somewhere else 25 | // apply changes 26 | data.versions.clear(); 27 | data.versions.putAll(map); 28 | data.versionsUpdated = true; 29 | } 30 | 31 | @Override 32 | public void write(ByteBuf buf, NetData data, byte version) { 33 | buf.writeInt(data.versions.size()); 34 | data.versions.forEach((aByte, integer) -> { 35 | buf.writeByte(aByte); 36 | buf.writeByte(integer); 37 | }); 38 | } 39 | 40 | @Override 41 | public byte getID() { 42 | return PacketConfig.DISCOVERY_PACKET; 43 | } 44 | 45 | @Override 46 | public byte getVer() { 47 | return CommonData.networkingVersion; 48 | } 49 | 50 | @Override 51 | public boolean doWrite(NetData config) { 52 | return config.purpose == PacketTask.CONFIG; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/resources/emotes.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | # Gui 3 | accessible class net/minecraft/client/gui/screens/packs/PackSelectionScreen$Watcher 4 | accessible field net/minecraft/client/gui/components/AbstractSelectionList MENU_LIST_BACKGROUND Lnet/minecraft/resources/Identifier; 5 | accessible field net/minecraft/client/gui/components/AbstractSelectionList INWORLD_MENU_LIST_BACKGROUND Lnet/minecraft/resources/Identifier; 6 | accessible field net/minecraft/client/gui/screens/recipebook/RecipeBookPage PAGE_FORWARD_SPRITES Lnet/minecraft/client/gui/components/WidgetSprites; 7 | accessible field net/minecraft/client/gui/screens/recipebook/RecipeBookPage PAGE_BACKWARD_SPRITES Lnet/minecraft/client/gui/components/WidgetSprites; 8 | accessible field net/minecraft/client/gui/screens/recipebook/RecipeBookComponent SEARCH_HINT Lnet/minecraft/network/chat/Component; 9 | extendable method net/minecraft/client/gui/components/AbstractWidget render (Lnet/minecraft/client/gui/GuiGraphics;IIF)V 10 | 11 | # Mannequins 12 | accessible method net/minecraft/world/entity/decoration/Mannequin setProfile (Lnet/minecraft/world/item/component/ResolvableProfile;)V 13 | accessible method net/minecraft/world/entity/decoration/Mannequin setHideDescription (Z)V 14 | extendable method net/minecraft/client/entity/ClientMannequin updateSkin ()V 15 | accessible field net/minecraft/client/entity/ClientMannequin skinLookup Ljava/util/concurrent/CompletableFuture; 16 | accessible method net/minecraft/client/entity/ClientMannequin setSkin (Lnet/minecraft/world/entity/player/PlayerSkin;)V 17 | 18 | # NeoForge network 19 | accessible field net/minecraft/server/level/ChunkMap entityMap Lit/unimi/dsi/fastutil/ints/Int2ObjectMap; 20 | accessible class net/minecraft/server/level/ChunkMap$TrackedEntity 21 | accessible field net/minecraft/server/level/ChunkMap$TrackedEntity seenBy Ljava/util/Set; 22 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "emotecraft", 4 | "version": "${version}", 5 | "name": "Emotecraft", 6 | "description": "${description}", 7 | "authors": [ 8 | "KosmX", 9 | "dima_dencep" 10 | ], 11 | "contributors": [ 12 | "Kale Ko", 13 | "ZigyTheBird", 14 | "BoBkiNN" 15 | ], 16 | "contact": { 17 | "issues": "https://github.com/KosmX/emotes/issues", 18 | "homepage": "https://docs.zigythebird.com/emotecraft/gettingstarted", 19 | "sources": "https://github.com/KosmX/emotes" 20 | }, 21 | "license": "GPL3", 22 | "icon": "emotecraft_mod_logo.png", 23 | "environment": "*", 24 | "entrypoints": { 25 | "main": [ 26 | "io.github.kosmx.emotes.fabric.EmotecraftFabricMod" 27 | ], 28 | "client": [ 29 | "io.github.kosmx.emotes.fabric.EmotecraftClientFabricMod" 30 | ], 31 | "modmenu": [ 32 | "io.github.kosmx.emotes.fabric.ModMenu" 33 | ] 34 | }, 35 | "mixins": [ 36 | "emotecraft-arch.mixins.json" 37 | ], 38 | "depends": { 39 | "fabric-command-api-v2": "*", 40 | "fabric-networking-api-v1": "*", 41 | "fabric-key-binding-api-v1": "*", 42 | "fabric-lifecycle-events-v1": "*", 43 | "fabric-rendering-v1": ">=12.5.1+fbe231d52c", 44 | "minecraft": ">=1.21.11", 45 | "player_animation_library": ">=1.1.4+mc.1.21.11", 46 | "fabricloader": ">=0.18.3" 47 | }, 48 | "breaks": { 49 | "bendable_cuboids": "<1.0.6+mc.1.21.11", 50 | "grimac": "*", 51 | "packetevents": "*" 52 | }, 53 | "custom": { 54 | "modmenu": { 55 | "links": { 56 | "modmenu.curseforge": "https://curseforge.com/minecraft/mc-mods/emotecraft", 57 | "modmenu.discord": "https://discord.gg/6NfdRuE", 58 | "modmenu.github_releases": "https://github.com/KosmX/emotes/releases", 59 | "modmenu.modrinth": "https://modrinth.com/mod/emotecraft" 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Release 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | type: 10 | description: 'alpha/beta/stable' 11 | required: false 12 | default: 'alpha' 13 | changelog: 14 | description: 'changelog' 15 | required: false 16 | default: '' 17 | 18 | permissions: 19 | contents: write 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-22.04 24 | steps: 25 | - name: Check for valid input 26 | run: | 27 | if ! ( [ "${{github.event.inputs.type}}" = "alpha" ] || [ "${{github.event.inputs.type}}" = "beta" ] || [ "${{github.event.inputs.type}}" = "stable" ] ) 28 | then 29 | return -1 30 | fi 31 | - name: checkout repository 32 | uses: actions/checkout@v6 33 | - name: Setup JDK 21 34 | uses: actions/setup-java@v5 35 | with: 36 | distribution: 'microsoft' 37 | java-version: 21 38 | - name: Setup Gradle 39 | uses: gradle/actions/setup-gradle@v5 40 | with: 41 | gradle-version: wrapper 42 | - name: Grant execute permission for gradlew 43 | run: chmod +x gradlew 44 | - name: Publish 45 | run: ./gradlew publish publishMods 46 | env: 47 | BUILD_NUMBER: ${{github.run_number}} 48 | RELEASE_TYPE: ${{github.event.inputs.type}} 49 | CHANGELOG: ${{github.event.inputs.changelog}} 50 | CURSEFORGE_TOKEN: ${{secrets.CURSEFORGE}} 51 | MODRINTH_TOKEN: ${{secrets.MODRINTH}} 52 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 53 | KOSMX_TOKEN: ${{secrets.KOSMX_TOKEN}} 54 | DISCORD_WEBHOOK: ${{secrets.DISCORD_WEBHOOK}} 55 | HANGAR_TOKEN: ${{secrets.HANGAR_TOKEN}} 56 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/CommonNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network; 2 | 3 | import com.zigythebird.playeranimcore.network.LegacyAnimationBinary; 4 | import io.netty.buffer.ByteBuf; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * I can't use Minecraft's string and uuid byte reader in a bukkit plugin, I need to implement these. 13 | * This can still here but it can be removed if unused 14 | */ 15 | public class CommonNetwork { 16 | public static String readString(ByteBuf buf) { 17 | return LegacyAnimationBinary.getString(buf); 18 | } 19 | 20 | public static void writeString(ByteBuf buf, String str) { 21 | if (str == null || str.isBlank()) { // Minor optimization to avoid writing empty lines 22 | buf.writeInt(0); 23 | return; 24 | } 25 | LegacyAnimationBinary.putString(buf, str); 26 | } 27 | 28 | public static List readList(ByteBuf buf, Function reader) { 29 | int count = buf.readInt(); 30 | List list = new ArrayList<>(count); 31 | for (int i = 0; i < count; i++) { 32 | list.add(reader.apply(buf)); 33 | } 34 | return list; 35 | } 36 | 37 | public static void writeList(ByteBuf buf, List elements, BiConsumer writter) { 38 | if (elements == null) { 39 | buf.writeInt(0); 40 | return; 41 | } 42 | 43 | buf.writeInt(elements.size()); 44 | for (T entry : elements) { 45 | writter.accept(buf, entry); 46 | } 47 | } 48 | 49 | public static boolean readBoolean(ByteBuf buf) { 50 | return buf.readByte() != 0; 51 | } 52 | 53 | public static void writeBoolean(ByteBuf buf, boolean bool) { 54 | buf.writeByte((byte) (bool ? 1 : 0)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/utils/WidgetOutliner.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.utils; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | import net.minecraft.client.gui.layouts.LayoutElement; 6 | import net.minecraft.client.renderer.RenderPipelines; 7 | import net.minecraft.resources.Identifier; 8 | 9 | public class WidgetOutliner { 10 | public static void renderOutline(GuiGraphics guiGraphics, LayoutElement element, int color) { 11 | Identifier headerSeparator = EmotecraftTexture.HEADER_SEPARATOR.identifier(Minecraft.getInstance().level != null); 12 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, headerSeparator, element.getX(), element.getY() - 1, 0.0F, 0.0F, element.getWidth(), 2, 32, 2, color); 13 | 14 | drawSeparatorRotated(guiGraphics, headerSeparator, element.getX() - 1, element.getY(), element.getHeight(), -90F, color); 15 | drawSeparatorRotated(guiGraphics, headerSeparator, element.getX() + element.getWidth() + 1, element.getY(), element.getHeight(), 90F, color); 16 | 17 | Identifier footerSeparator = EmotecraftTexture.FOOTER_SEPARATOR.identifier(Minecraft.getInstance().level != null); 18 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, footerSeparator, element.getX(), element.getY() + element.getHeight() - 1, 0.0F, 0.0F, element.getWidth(), 2, 32, 2, color); 19 | } 20 | 21 | protected static void drawSeparatorRotated(GuiGraphics guiGraphics, Identifier separator, int x, int y, int size, float angle, int color) { 22 | guiGraphics.pose().pushMatrix(); 23 | guiGraphics.pose().translate(x, y + size / 2.0F); 24 | guiGraphics.pose().rotate((float) Math.toRadians(angle)); 25 | guiGraphics.pose().translate(-size / 2.0F, 0); 26 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, separator, 0, 0, 0.0F, 0.0F, size, 2, 32, 2, color); 27 | guiGraphics.pose().popMatrix(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /emotesAPI/src/test/java/io/github/kosmx/emotes/testing/common/BiMapTest.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.testing.common; 2 | 3 | import io.github.kosmx.emotes.common.tools.BiMap; 4 | import it.unimi.dsi.fastutil.Pair; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.Random; 10 | 11 | public class BiMapTest { 12 | @Test 13 | @DisplayName("BiMap function verification") 14 | public void test(){ 15 | BiMap biMap = new BiMap<>(), biMap1 = new BiMap<>(); 16 | 17 | Random random = new Random(); 18 | 19 | //basic operations 20 | 21 | Pair element; 22 | 23 | Pair nullPair = Pair.of(null, null); 24 | 25 | element = biMap.put("a", 1); 26 | Assertions.assertEquals(element, nullPair, "there were no element like that in the map"); 27 | 28 | element = biMap.put("b", 2); 29 | Assertions.assertEquals(element, nullPair, "there were no element like that in the map"); 30 | 31 | element = biMap.put("a", 2); 32 | Assertions.assertEquals(element, Pair.of("b", 1)); 33 | 34 | 35 | Assertions.assertFalse(biMap.contains(Pair.of("a", 1))); 36 | Assertions.assertFalse(biMap.contains(Pair.of("b", 2))); 37 | Assertions.assertTrue(biMap.contains(Pair.of("a", 2))); 38 | Assertions.assertEquals(1, biMap.size()); 39 | 40 | Assertions.assertEquals(biMap.removeL("a"), 2); 41 | 42 | 43 | int i = random.nextInt(1000) + 500; //500-1500 elements. that will be enough 44 | for(int n = 0; n < i; n++){ 45 | String str = Double.toString(random.nextDouble()); 46 | i = random.nextInt(); 47 | biMap.add(Pair.of(str, i)); 48 | biMap1.put(str, i); 49 | } 50 | Assertions.assertEquals(biMap, biMap1); 51 | Assertions.assertEquals(biMap.hashCode(), biMap1.hashCode()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/AbstractServerNetwork.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.api.proxy.INetworkInstance; 4 | import io.github.kosmx.emotes.common.CommonData; 5 | import io.github.kosmx.emotes.common.network.EmotePacket; 6 | import io.github.kosmx.emotes.server.network.EmotePlayTracker; 7 | import io.github.kosmx.emotes.server.network.IServerNetworkInstance; 8 | import net.minecraft.world.entity.Avatar; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.Map; 13 | import java.util.UUID; 14 | 15 | public abstract class AbstractServerNetwork implements INetworkInstance, IServerNetworkInstance { 16 | private final EmotePlayTracker emotePlayTracker = new EmotePlayTracker(); 17 | 18 | @NotNull 19 | protected abstract EmotesMixinConnection getServerConnection(); 20 | 21 | @NotNull 22 | protected abstract Avatar getAvatar(); 23 | 24 | @Override 25 | public Map getRemoteVersions() { 26 | return getServerConnection().emotecraft$getRemoteVersions(); 27 | } 28 | 29 | @Override 30 | public void setVersions(Map map) { 31 | getServerConnection().emotecraft$setVersions(map); 32 | } 33 | 34 | @Override 35 | public boolean isServerTrackingPlayState() { 36 | return true; // MC server does track this 37 | } 38 | 39 | @Override 40 | public int maxDataSize() { 41 | return CommonData.MAX_PACKET_SIZE - 16; // channel ID is 12, one extra int makes it 16 (string) 42 | } 43 | 44 | @Override 45 | public EmotePlayTracker getEmoteTracker() { 46 | return this.emotePlayTracker; 47 | } 48 | 49 | @Override 50 | public void sendMessage(EmotePacket.Builder builder, @Nullable UUID target) { 51 | sendPlayMessage(builder.setVersion(getRemoteVersions()).build()); 52 | } 53 | 54 | public abstract void sendPlayMessage(EmotePacket bytes); 55 | } 56 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/EmoteHeaderPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import io.github.kosmx.emotes.common.network.CommonNetwork; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | import java.util.List; 8 | 9 | public class EmoteHeaderPacket extends AbstractNetworkPacket{ 10 | @Override 11 | public byte getID() { 12 | return PacketConfig.HEADER_PACKET; 13 | } 14 | 15 | @Override 16 | public byte getVer() { 17 | return 2; 18 | } 19 | 20 | @Override 21 | public void read(ByteBuf byteBuf, NetData config, byte version) { 22 | config.extraData.put("name", CommonNetwork.readString(byteBuf)); 23 | config.extraData.put("description", CommonNetwork.readString(byteBuf)); 24 | config.extraData.put("author", CommonNetwork.readString(byteBuf)); 25 | if (version >= 2) { 26 | config.extraData.put("folderpath", CommonNetwork.readString(byteBuf)); 27 | config.extraData.put("bages", CommonNetwork.readList(byteBuf, CommonNetwork::readString)); 28 | } 29 | } 30 | 31 | @Override 32 | @SuppressWarnings("unchecked") 33 | public void write(ByteBuf byteBuf, NetData config, byte version) { 34 | assert config.emoteData != null; 35 | CommonNetwork.writeString(byteBuf, (String) config.emoteData.data().getRaw("name")); 36 | CommonNetwork.writeString(byteBuf, (String) config.emoteData.data().getRaw("description")); 37 | CommonNetwork.writeString(byteBuf, (String) config.emoteData.data().getRaw("author")); 38 | if (version >= 2) { 39 | CommonNetwork.writeString(byteBuf, (String) config.emoteData.data().getRaw("folderpath")); 40 | CommonNetwork.writeList(byteBuf, (List) config.emoteData.data().getRaw("bages"), CommonNetwork::writeString); 41 | } 42 | } 43 | 44 | @Override 45 | public boolean doWrite(NetData config) { 46 | return config.emoteData != null && config.purpose.exchangeHeader; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/api/events/client/ClientEmoteAPI.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.api.events.client; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import io.github.kosmx.emotes.api.services.IEmotecraftService; 5 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 6 | import org.jetbrains.annotations.Nullable; 7 | import java.util.Collection; 8 | 9 | public abstract class ClientEmoteAPI implements IEmotecraftService { 10 | /** 11 | * Stop play an emote. 12 | */ 13 | public static boolean stopEmote() { 14 | return ClientEmoteAPI.playEmote(null); 15 | } 16 | 17 | /** 18 | * Start playing an emote. 19 | * @param animation animation, null to stop playing. 20 | * @return Can the emote be played: this doesn't check server-side verification 21 | */ 22 | public static boolean playEmote(@Nullable Animation animation) { 23 | return ClientEmoteAPI.playEmote(animation, 0); 24 | } 25 | 26 | /** 27 | * Start playing an emote. 28 | * @param animation animation, null to stop playing. 29 | * @param tick First tick 30 | * @return Can the emote be played: this doesn't check server-side verification 31 | */ 32 | public static boolean playEmote(@Nullable Animation animation, float tick) { 33 | return INSTANCE.playEmoteImpl(animation, tick); 34 | } 35 | 36 | /** 37 | * A list of client-side active emotes. 38 | * You can not modify the list. 39 | * @return Client-side active emotes 40 | */ 41 | public static Collection clientEmoteList() { 42 | return INSTANCE.clientEmoteListImpl(); 43 | } 44 | 45 | // ---- IMPLEMENTATION ---- // 46 | 47 | protected static final ClientEmoteAPI INSTANCE = ServiceLoaderUtil.loadService(ClientEmoteAPI.class); 48 | 49 | protected abstract boolean playEmoteImpl(Animation animation, float tick); 50 | 51 | protected abstract Collection clientEmoteListImpl(); 52 | 53 | @Override 54 | public boolean isActive() { 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /emotesMc/src/main/java/io/github/kosmx/emotes/mc/McUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.mc; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParser; 5 | import com.mojang.serialization.JsonOps; 6 | import io.github.kosmx.emotes.common.CommonData; 7 | import net.minecraft.core.HolderLookup; 8 | import net.minecraft.core.RegistryAccess; 9 | import net.minecraft.network.chat.CommonComponents; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.network.chat.ComponentSerialization; 12 | import net.minecraft.resources.Identifier; 13 | 14 | public class McUtils { 15 | public static final Component MOD_NAME = Component.literal(CommonData.MOD_NAME); 16 | public static final Component SLASH = Component.literal("/"); 17 | 18 | public static Component fromJson(String json, HolderLookup.Provider registries) { 19 | if (json == null || json.isBlank()) return CommonComponents.EMPTY; 20 | 21 | try { 22 | return ComponentSerialization.CODEC.parse( 23 | registries.createSerializationContext(JsonOps.INSTANCE), 24 | JsonParser.parseString(json) 25 | ).getOrThrow(); 26 | } catch (Throwable e) { 27 | return Component.nullToEmpty(json); 28 | } 29 | } 30 | 31 | public static Component fromJson(Object obj) { 32 | return McUtils.fromJson(obj, RegistryAccess.EMPTY); 33 | } 34 | 35 | public static Component fromJson(Object obj, HolderLookup.Provider registries) { 36 | return switch (obj) { 37 | case null -> CommonComponents.EMPTY; 38 | 39 | case String string -> McUtils.fromJson(string, registries); 40 | 41 | case JsonElement element -> ComponentSerialization.CODEC.parse( 42 | registries.createSerializationContext(JsonOps.INSTANCE), element 43 | ).getOrThrow(); 44 | 45 | default -> throw new IllegalArgumentException("Can not create Text from " + obj.getClass().getName()); 46 | }; 47 | } 48 | 49 | public static Identifier newIdentifier(String id) { 50 | return Identifier.fromNamespaceAndPath(CommonData.MOD_ID, id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/network/NetworkPlatformTools.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.network; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import io.github.kosmx.emotes.common.network.EmotePacket; 5 | import io.github.kosmx.emotes.common.tools.ServiceLoaderUtil; 6 | import io.github.kosmx.emotes.mc.McUtils; 7 | import net.minecraft.network.protocol.Packet; 8 | import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; 9 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 10 | import net.minecraft.resources.Identifier; 11 | import net.minecraft.server.MinecraftServer; 12 | import net.minecraft.server.level.ServerPlayer; 13 | import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; 14 | import net.minecraft.world.entity.Entity; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.Collection; 18 | 19 | public interface NetworkPlatformTools { 20 | NetworkPlatformTools INSTANCE = ServiceLoaderUtil.loadServices(NetworkPlatformTools.class).findAny().orElseThrow(); 21 | 22 | CustomPacketPayload.Type EMOTE_CHANNEL_ID = new CustomPacketPayload.Type<>(McUtils.newIdentifier(CommonData.playEmoteID)); 23 | CustomPacketPayload.Type STREAM_CHANNEL_ID = new CustomPacketPayload.Type<>(McUtils.newIdentifier(CommonData.emoteStreamID)); 24 | 25 | boolean canSendPlay(ServerPlayer player, Identifier channel); 26 | boolean canSendConfig(ServerConfigurationPacketListenerImpl player, Identifier channel); 27 | Collection getTrackedBy(Entity entity); 28 | 29 | static @NotNull Packet createClientboundPacket(@NotNull CustomPacketPayload.Type id, @NotNull EmotePacket packet) { 30 | return new ClientboundCustomPayloadPacket(new EmotePacketPayload(id, packet)); 31 | } 32 | 33 | MinecraftServer getServer(); 34 | 35 | static @NotNull Packet playPacket(@NotNull EmotePacket packet) { 36 | return createClientboundPacket(EMOTE_CHANNEL_ID, packet); 37 | } 38 | 39 | static @NotNull Packet streamPacket(@NotNull EmotePacket packet) { 40 | return createClientboundPacket(STREAM_CHANNEL_ID, packet); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blender/README.md: -------------------------------------------------------------------------------- 1 | # Player animation tools 2 | 3 | The `.blend` files are [Blender](https://www.blender.org/) projects. 4 | To use `emote_creator_bend_item_scale.blend` you need Blender version at least 5.0. For `emote_creator` and `emote_creator_bend` you can use Blender version from 2.83 up to 4.x. 5 | [Emotecraft wiki](https://docs.zigythebird.com/emotecraft/creatingemotes/) if you're stuck. 6 | 7 | `.bbmodel` files are models for [Blockbench](https://blockbench.net/). You can use them as well. 8 | To use them, you'll need to install [GeckoLib](https://geckolib.com/) plugin first. 9 | The Blockbench emotes support is not very good on MC versions <1.21.7 10 | 11 | Models labled with `_bend` allow you to bend some bones like in Minecraft Story Mode, and the rest of the labels should be self explanatory. 12 | Keep in mind that the visual for bending is incorrect in Blender/Blockbench, there won't be any gaps created by bending a bone in-game. 13 | All Blockbench models support scaling. 14 | > [!WARNING] 15 | > Scale animation will be visible only on Minecraft version 1.21.4+ 16 | 17 | > [!CAUTION] 18 | > BENDING ON THE Z AXIS IS NOT SUPPORTED!!! 19 | > And by Z axis I mean the one facing forward, I need to specify this since Blender uses a different coordinate system than MC. 20 | > Make sure every bone's Z rotation value is always set to 0 in Blockbench and the same for Y rotation values in Blender! 21 | 22 | > [!TIP] 23 | > It's possible to add custom bones to a Blockbench model in order to animate player accessories IF it's supported by playerAnimator or another mod. 24 | > For example you can add a bone called elytra to the model and animate the elytra that way! 25 | > Cape rotations are also applied to elytras, but there won't be any bending. 26 | > The elytra bone's priority is greater than cape bone's for animating elytra but both can influence the elytra at the same time. 27 | 28 | ### If you don't like these 29 | You can create your own program or edit the file by hand 30 | The emote format documentation is [here](https://github.com/KosmX/emotes/wiki/Emote.json) 31 | [Here](https://github.com/bigguy345/Blender-Minecraft-Animation/tree/main) is a Blender addon that lets you import and save animations + bend limbs on multiple axes. 32 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/network/EmotePlayTracker.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.network; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import it.unimi.dsi.fastutil.Pair; 5 | import org.jetbrains.annotations.Nullable; 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | 9 | /** 10 | * Server side emote state tracking 11 | * It uses {@link Instant} 12 | * By using instant, tracking is mostly immune to server lags, tick drops 13 | * However susceptible to system clock changes. 14 | * And less demanding for a large server 15 | * 16 | */ 17 | public class EmotePlayTracker { 18 | private Animation currentEmote = null; 19 | private Instant startTime = null; 20 | private boolean isForced = false; 21 | 22 | /** 23 | * Set the currently played emote. 24 | * @param data Emote, null if stop playing 25 | */ 26 | public void setPlayedEmote(@Nullable Animation data, boolean isForced) { 27 | this.currentEmote = data; 28 | 29 | if (data == null) { 30 | this.startTime = null; 31 | this.isForced = false; 32 | } else { 33 | this.startTime = Instant.now(); 34 | this.isForced = isForced; 35 | } 36 | } 37 | 38 | /** 39 | * Is the currently played emote forced 40 | * Returns false if not playing emote 41 | * a.k.a. disallow the user play a different emote 42 | * @return true if forced, false if not playing any emote. 43 | */ 44 | public boolean isForced() { 45 | if (getPlayedEmote() != null) { 46 | return isForced; 47 | } else return false; 48 | } 49 | 50 | /** 51 | * Get the currently played emote and the tick time 52 | * @return null if not playing emote 53 | */ 54 | @Nullable 55 | public Pair getPlayedEmote() { 56 | if (currentEmote == null) return null; 57 | float tick = Duration.between(startTime, Instant.now()).toMillis() / 50F; 58 | if (!currentEmote.isPlayingAt(tick)) { 59 | currentEmote = null; 60 | startTime = null; 61 | isForced = false; 62 | return null; 63 | } 64 | return Pair.of(currentEmote, tick); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/PlatformTools.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes; 2 | 3 | import com.zigythebird.playeranim.PlayerAnimLibPlatform; 4 | import io.github.kosmx.emotes.api.proxy.INetworkInstance; 5 | import io.github.kosmx.emotes.arch.network.client.ClientNetwork; 6 | import io.github.kosmx.emotes.main.config.ClientConfig; 7 | import io.github.kosmx.emotes.mc.McUtils; 8 | import io.github.kosmx.emotes.server.config.Serializer; 9 | import io.github.kosmx.emotes.server.services.InstanceService; 10 | import net.minecraft.client.CameraType; 11 | import net.minecraft.client.Minecraft; 12 | import net.minecraft.client.gui.components.toasts.SystemToast; 13 | import net.minecraft.client.multiplayer.ClientLevel; 14 | import net.minecraft.network.chat.Component; 15 | import net.minecraft.util.Util; 16 | import net.minecraft.world.entity.Avatar; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.UUID; 20 | 21 | public final class PlatformTools { 22 | public static final boolean HAS_SEARCHABLES = PlayerAnimLibPlatform.isModLoaded("searchables"); 23 | 24 | public static INetworkInstance getClientNetworkController() { 25 | return ClientNetwork.INSTANCE; 26 | } 27 | 28 | public static @Nullable Avatar getAvatarFromUUID(UUID uuid) { 29 | ClientLevel level = Minecraft.getInstance().level; 30 | if (level == null) return null; 31 | return (Avatar) level.getEntity(uuid); 32 | } 33 | 34 | public static void openExternalEmotesDir() { 35 | Util.getPlatform().openPath(InstanceService.INSTANCE.getExternalEmoteDir()); 36 | } 37 | 38 | public static ClientConfig getConfig() { 39 | return (ClientConfig) Serializer.getConfig(); 40 | } 41 | 42 | public static CameraType getPerspective() { 43 | return Minecraft.getInstance().options.getCameraType(); 44 | } 45 | 46 | public static void setPerspective(CameraType p) { 47 | Minecraft.getInstance().options.setCameraType(p); 48 | } 49 | 50 | public static void addToast(Component title, Component message) { 51 | SystemToast.add(Minecraft.getInstance().getToastManager(), SystemToast.SystemToastId.WORLD_BACKUP, title, message); 52 | } 53 | 54 | public static void addToast(Component message) { 55 | PlatformTools.addToast(McUtils.MOD_NAME, message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /paper/src/main/java/io/github/kosmx/emotes/bukkit/network/BukkitNetworkInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.bukkit.network; 2 | 3 | import io.github.kosmx.emotes.api.proxy.AbstractNetworkInstance; 4 | import io.github.kosmx.emotes.bukkit.BukkitWrapper; 5 | import io.github.kosmx.emotes.common.CommonData; 6 | import io.github.kosmx.emotes.common.network.EmotePacket; 7 | import io.github.kosmx.emotes.server.network.EmotePlayTracker; 8 | import io.github.kosmx.emotes.server.network.IServerNetworkInstance; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.buffer.Unpooled; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.entity.Avatar; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.util.UUID; 16 | 17 | public class BukkitNetworkInstance extends AbstractNetworkInstance implements IServerNetworkInstance { 18 | private static final BukkitWrapper PLUGIN = BukkitWrapper.getPlugin(BukkitWrapper.class); 19 | 20 | private final EmotePlayTracker emotePlayTracker = new EmotePlayTracker(); 21 | protected final Avatar avatar; 22 | 23 | public BukkitNetworkInstance(Avatar avatar) { 24 | this.avatar = avatar; 25 | } 26 | 27 | @Override 28 | public EmotePlayTracker getEmoteTracker() { 29 | return this.emotePlayTracker; 30 | } 31 | 32 | @Override 33 | public void sendMessage(EmotePacket packet, @Nullable UUID target) { 34 | if (!(this.avatar instanceof ServerPlayer player)) { 35 | CommonData.LOGGER.error("Attempt to send a packet of an unsupported entity: {}!", this.avatar); 36 | return; 37 | } 38 | ByteBuf buf = Unpooled.buffer(); 39 | try { 40 | packet.write(buf); 41 | byte[] bytes = new byte[buf.readableBytes()]; 42 | buf.readBytes(bytes); 43 | player.getBukkitEntity().sendPluginMessage(PLUGIN, BukkitWrapper.EMOTE_PACKET, bytes); 44 | } finally { 45 | buf.release(); 46 | } 47 | } 48 | 49 | @Override 50 | public boolean isActive() { 51 | return this.avatar instanceof ServerPlayer; 52 | } 53 | 54 | @Override 55 | @SuppressWarnings("deprecation") 56 | public void presenceResponse() { 57 | super.presenceResponse(); 58 | ServerSideEmotePlay.getInstance().presenceResponse(this, trackPlayState()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/neoforge/services/NeoPermissionService.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.neoforge.services; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import io.github.kosmx.emotes.mc.ServerCommands; 5 | import io.github.kosmx.emotes.mc.services.IPermissionService; 6 | import net.minecraft.commands.CommandSourceStack; 7 | import net.neoforged.api.distmarker.Dist; 8 | import net.neoforged.bus.api.SubscribeEvent; 9 | import net.neoforged.fml.common.EventBusSubscriber; 10 | import net.neoforged.fml.loading.FMLLoader; 11 | import net.neoforged.neoforge.server.permission.PermissionAPI; 12 | import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; 13 | import net.neoforged.neoforge.server.permission.nodes.PermissionNode; 14 | import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Optional; 21 | 22 | @EventBusSubscriber(modid = CommonData.MOD_ID, value = Dist.DEDICATED_SERVER) 23 | public class NeoPermissionService implements IPermissionService { 24 | private static final Map> NODES = new HashMap<>(); 25 | 26 | @Override 27 | public Optional getPermissionValue(@NotNull CommandSourceStack source, @NotNull String permission) { 28 | if (!NeoPermissionService.NODES.containsKey(permission) || !source.isPlayer()) { 29 | return Optional.empty(); 30 | } 31 | return Optional.of(PermissionAPI.getPermission( 32 | Objects.requireNonNull(source.getPlayer()), NODES.get(permission) 33 | )); 34 | } 35 | 36 | @Override 37 | public boolean isActive() { 38 | return FMLLoader.getCurrent().getDist().isDedicatedServer(); 39 | } 40 | 41 | @SubscribeEvent 42 | public static void onRegisterPermissionNodes(PermissionGatherEvent.Nodes event) { 43 | for (String permission : ServerCommands.PERMISSIONS) { 44 | PermissionNode node = new PermissionNode<>(CommonData.MOD_ID, permission, PermissionTypes.BOOLEAN, 45 | (arg, uUID, permissionDynamicContexts) -> false 46 | ); 47 | 48 | event.addNodes(node); 49 | NeoPermissionService.NODES.put(permission, node); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /emotesAPI/src/test/java/io/github/kosmx/emotes/testing/common/RandomEmoteData.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.testing.common; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import com.zigythebird.playeranimcore.animation.ExtraAnimationData; 5 | import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation; 6 | import com.zigythebird.playeranimcore.animation.keyframe.Keyframe; 7 | import com.zigythebird.playeranimcore.easing.EasingType; 8 | import com.zigythebird.playeranimcore.loading.UniversalAnimLoader; 9 | import it.unimi.dsi.fastutil.Pair; 10 | import team.unnamed.mocha.parser.ast.FloatExpression; 11 | 12 | import java.util.Collections; 13 | import java.util.HashMap; 14 | import java.util.Random; 15 | 16 | public class RandomEmoteData { 17 | /** 18 | * Creates two identical random emote. 19 | * @return Pair 20 | */ 21 | public static Pair generateEmotes() { 22 | Random random = new Random(); 23 | int length = random.nextInt()%1000 + 2000; //make some useable values 24 | 25 | BoneAnimation builder1Bone = new BoneAnimation(); 26 | BoneAnimation builder2Bone = new BoneAnimation(); 27 | 28 | int count = random.nextInt()%118 + 128; 29 | for(int i = 0; i < count; i++) { 30 | int pos = Math.abs(random.nextInt() % length); 31 | FloatExpression val = FloatExpression.of(Math.abs(random.nextInt() % length)); 32 | EasingType ease = EasingType.fromId((byte) (random.nextInt() % 48)); 33 | builder1Bone.positionKeyFrames().xKeyframes().add(new Keyframe(pos, Collections.singletonList(val), Collections.singletonList(val), ease)); 34 | builder2Bone.positionKeyFrames().xKeyframes().add(new Keyframe(pos, Collections.singletonList(val), Collections.singletonList(val), ease)); 35 | } 36 | 37 | Animation builder1 = new Animation(new ExtraAnimationData(), length, Animation.LoopType.PLAY_ONCE, 38 | Collections.singletonMap("head", builder1Bone), UniversalAnimLoader.NO_KEYFRAMES, new HashMap<>(), new HashMap<>() 39 | ); 40 | Animation builder2 = new Animation(new ExtraAnimationData(), length, Animation.LoopType.PLAY_ONCE, 41 | Collections.singletonMap("head", builder2Bone), UniversalAnimLoader.NO_KEYFRAMES, new HashMap<>(), new HashMap<>() 42 | ); 43 | return Pair.of(builder1, builder2); 44 | } 45 | } -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/utils/EmotecraftTexture.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.utils; 2 | 3 | import io.github.kosmx.emotes.mc.McUtils; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.components.AbstractSelectionList; 6 | import net.minecraft.client.gui.screens.Screen; 7 | import net.minecraft.resources.Identifier; 8 | import net.minecraft.server.packs.resources.Resource; 9 | import net.minecraft.server.packs.resources.ResourceManager; 10 | 11 | import java.util.Optional; 12 | 13 | public enum EmotecraftTexture { 14 | HEADER_SEPARATOR(Screen.HEADER_SEPARATOR, Screen.INWORLD_HEADER_SEPARATOR), 15 | FOOTER_SEPARATOR(Screen.FOOTER_SEPARATOR, Screen.INWORLD_FOOTER_SEPARATOR), 16 | MENU_LIST_BACKGROUND(AbstractSelectionList.MENU_LIST_BACKGROUND, AbstractSelectionList.INWORLD_MENU_LIST_BACKGROUND); 17 | 18 | private final Identifier minecraft; 19 | private final Identifier minecraftInWorld; 20 | 21 | private final Identifier emotecraft; 22 | private final Identifier emotecraftInWorld; 23 | 24 | EmotecraftTexture(Identifier minecraft, Identifier minecraftInWorld) { 25 | this(minecraft, minecraftInWorld, McUtils.newIdentifier(minecraft.getPath()), McUtils.newIdentifier(minecraftInWorld.getPath())); 26 | } 27 | 28 | EmotecraftTexture(Identifier minecraft, Identifier minecraftInWorld, Identifier emotecraft, Identifier emotecraftInWorld) { 29 | this.minecraft = minecraft; 30 | this.minecraftInWorld = minecraftInWorld; 31 | this.emotecraft = emotecraft; 32 | this.emotecraftInWorld = emotecraftInWorld; 33 | } 34 | 35 | public Identifier identifier(boolean inWorld) { 36 | ResourceManager manager = Minecraft.getInstance().getResourceManager(); 37 | if (inWorld) { 38 | Optional resourceInWorld = manager.getResource(this.emotecraftInWorld); 39 | if (resourceInWorld.isPresent()) return this.emotecraftInWorld; 40 | 41 | Optional resource = manager.getResource(this.emotecraft); 42 | if (resource.isPresent()) return this.emotecraft; 43 | 44 | return this.minecraftInWorld; 45 | } else { 46 | Optional resource = manager.getResource(this.emotecraft); 47 | return resource.isEmpty() ? this.minecraft : this.emotecraft; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/emotePlay/MinecraftNbsPlayer.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.emotePlay; 2 | 3 | import com.zigythebird.playeranim.animation.PlayerAnimationController; 4 | import io.github.kosmx.emotes.arch.screen.utils.UnsafeMannequin; 5 | import io.github.kosmx.emotes.common.nbsplayer.NbsPlayer; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.resources.sounds.SoundInstance; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraft.world.entity.Avatar; 10 | import net.raphimc.noteblocklib.model.Note; 11 | import net.raphimc.noteblocklib.model.Song; 12 | import net.raphimc.noteblocklib.util.TimerHack; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class MinecraftNbsPlayer extends NbsPlayer { 16 | protected final Avatar avatar; 17 | 18 | public MinecraftNbsPlayer(PlayerAnimationController controller, Song song) { 19 | super(song, controller); 20 | this.avatar = controller.getAvatar(); 21 | } 22 | 23 | @Override 24 | public void start(int delay, int tick) { 25 | TimerHack.ENABLED = false; 26 | super.start(delay, tick); 27 | } 28 | 29 | @Override 30 | protected boolean shouldTick() { 31 | if (this.avatar instanceof UnsafeMannequin) return super.shouldTick(); 32 | Minecraft mc = Minecraft.getInstance(); 33 | if (mc.level != this.avatar.level()) { 34 | stop(); 35 | return false; 36 | } 37 | return !mc.isPaused() && super.shouldTick(); 38 | } 39 | 40 | public @Nullable Component getNowPlaying() { 41 | String author = getSong().getAuthorOr(getSong().getOriginalAuthorOr("")); 42 | String name = getSong().getTitleOrFileNameOr(""); 43 | 44 | if (author.isEmpty()) { 45 | if (!name.isEmpty()) { 46 | return Component.literal(name); 47 | } else { 48 | return null; 49 | } 50 | } else if (!name.isEmpty()) { 51 | return Component.literal(String.format("%s - %s", author, name)); 52 | } 53 | 54 | return null; 55 | } 56 | 57 | @Override 58 | protected void playNote(Note note) { 59 | SoundInstance sound = InstrumentConventer.getInstrument(note, this.avatar.position()); 60 | Minecraft.getInstance().execute(() -> this.avatar.emotecraft$playRawSound(sound)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/network/objects/SongPacket.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.network.objects; 2 | 3 | import io.github.kosmx.emotes.common.nbsplayer.LegacyNBSPacket; 4 | import io.github.kosmx.emotes.common.network.PacketConfig; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.ByteBufInputStream; 7 | import io.netty.buffer.ByteBufOutputStream; 8 | import net.raphimc.noteblocklib.NoteBlockLib; 9 | import net.raphimc.noteblocklib.format.SongFormat; 10 | import net.raphimc.noteblocklib.format.nbs.model.NbsSong; 11 | import net.raphimc.noteblocklib.model.Song; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | 17 | @SuppressWarnings("deprecation") 18 | public class SongPacket extends AbstractNetworkPacket { 19 | @Override 20 | public byte getID() { 21 | return PacketConfig.NBS_CONFIG; 22 | } 23 | 24 | @Override 25 | public byte getVer() { 26 | return 2; // Ver0 means NO sound 27 | } 28 | 29 | @Override 30 | public void read(ByteBuf buf, NetData config, byte version) throws IOException { 31 | Song song = switch (version) { 32 | case 2 -> { 33 | try (InputStream is = new ByteBufInputStream(buf)) { 34 | yield NoteBlockLib.readSong(is, SongFormat.NBS); 35 | } catch (Exception e) { 36 | throw new IOException(e); 37 | } 38 | } 39 | 40 | case 1 -> LegacyNBSPacket.read(buf); 41 | default -> null; 42 | }; 43 | config.extraData.put("song", song); 44 | } 45 | 46 | @Override 47 | public void write(ByteBuf buf, NetData config, byte version) throws IOException { 48 | assert config.emoteData != null; 49 | 50 | Song song = (Song) config.emoteData.data().getRaw("song"); 51 | if (version > 1) { 52 | try (OutputStream os = new ByteBufOutputStream(buf)) { 53 | NoteBlockLib.writeSong(song, os); 54 | } catch (Exception e) { 55 | throw new IOException(e); 56 | } 57 | } else { 58 | LegacyNBSPacket.write((NbsSong) song, buf); 59 | } 60 | } 61 | 62 | @Override 63 | public boolean doWrite(NetData config) { 64 | return config.versions.get(this.getID()) != 0 && config.emoteData != null && config.emoteData.data().has("song"); 65 | } 66 | 67 | @Override 68 | public boolean isOptional() { 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/tools/ServiceLoaderUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.tools; 2 | 3 | import io.github.kosmx.emotes.api.services.IEmotecraftService; 4 | import io.github.kosmx.emotes.common.CommonData; 5 | 6 | import java.util.Comparator; 7 | import java.util.Optional; 8 | import java.util.ServiceLoader; 9 | import java.util.function.Supplier; 10 | import java.util.stream.Stream; 11 | 12 | public class ServiceLoaderUtil { 13 | private static final Comparator COMPARATOR = Comparator.comparingInt(IEmotecraftService::getPriority); 14 | 15 | public static final int DEFAULT_PRIORITY = 0; 16 | public static final int HIGHEST_PRIORITY = 1000; 17 | public static final int LOWEST_PRIORITY = -1000; 18 | 19 | public static Stream loadServices(Class serviceClass) { 20 | ModuleLayer layer = serviceClass.getModule().getLayer(); // NeoForge compat? 21 | 22 | ServiceLoader loader = layer == null ? ServiceLoader.load(serviceClass, 23 | serviceClass.getClassLoader() 24 | ) : ServiceLoader.load(layer, serviceClass); 25 | 26 | return loader.stream() 27 | .map(ServiceLoader.Provider::get) 28 | .filter(service -> !(service instanceof IEmotecraftService emotecraftService) || emotecraftService.isActive()); 29 | } 30 | 31 | public static Stream loadServicesSorted(Class serviceClass) { 32 | return ServiceLoaderUtil.loadServices(serviceClass).sorted(COMPARATOR.reversed()); 33 | } 34 | 35 | public static T loadService(Class serviceClass, Supplier defaultService) { 36 | return ServiceLoaderUtil.loadOptionalService(serviceClass).orElseGet(defaultService); 37 | } 38 | 39 | public static T loadService(Class serviceClass) { 40 | return ServiceLoaderUtil.loadOptionalService(serviceClass).orElseThrow(); 41 | } 42 | 43 | public static Optional loadOptionalService(Class serviceClass) { 44 | Optional optional = ServiceLoaderUtil.loadServices(serviceClass).max(COMPARATOR); 45 | optional.ifPresent(service -> CommonData.LOGGER.debug("Selected service {} for {}", toString(service), serviceClass.getName())); 46 | return optional; 47 | } 48 | 49 | private static String toString(IEmotecraftService service) { 50 | return service.getName() + " (priority " + service.getPriority() + ")"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/nbsplayer/NbsPlayer.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common.nbsplayer; 2 | 3 | import com.zigythebird.playeranimcore.animation.AnimationController; 4 | import com.zigythebird.playeranimcore.enums.State; 5 | import io.github.kosmx.emotes.common.CommonData; 6 | import net.raphimc.noteblocklib.format.nbs.model.NbsSong; 7 | import net.raphimc.noteblocklib.model.Note; 8 | import net.raphimc.noteblocklib.model.Song; 9 | import net.raphimc.noteblocklib.player.SongPlayer; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.List; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.ScheduledExecutorService; 15 | 16 | public abstract class NbsPlayer extends SongPlayer { 17 | private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(5, 18 | Thread.ofVirtual().name("Emotecraft-NBSplayer-", 0).factory() 19 | ); 20 | 21 | @Nullable 22 | protected final AnimationController controller; 23 | 24 | protected int loopCount = 0; 25 | private boolean firstSongPlayed; 26 | 27 | public NbsPlayer(Song song, @Nullable AnimationController controller) { 28 | super(song); 29 | this.controller = controller; 30 | setCustomScheduler(EXECUTOR); 31 | } 32 | 33 | @Override 34 | protected void playNotes(List notes) { 35 | this.firstSongPlayed = true; 36 | for (Note note : notes) playNote(note); 37 | } 38 | 39 | @Override 40 | protected boolean shouldTick() { 41 | if (this.controller == null) return true; 42 | 43 | if (!this.controller.isActive()) { 44 | stop(); 45 | return false; 46 | } 47 | return this.controller.getAnimationState() == State.RUNNING; 48 | } 49 | 50 | protected abstract void playNote(Note note); 51 | 52 | @Override 53 | protected void onSongFinished() { 54 | super.onSongFinished(); 55 | 56 | if (getSong() instanceof NbsSong nbsSong) { 57 | if (nbsSong.isLoop() && (this.loopCount < nbsSong.getMaxLoopCount() || nbsSong.getMaxLoopCount() == 0)) { 58 | this.loopCount++; 59 | this.start((int) (1000 / this.getCurrentTicksPerSecond()), nbsSong.getLoopStartTick()); 60 | } 61 | } 62 | } 63 | 64 | public boolean isFirstSongPlayed() { 65 | return this.firstSongPlayed; 66 | } 67 | 68 | @Override 69 | protected void onTickException(Throwable e) { 70 | CommonData.LOGGER.warn("An error occurred while playing nbs!", e); 71 | stop(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/network/ClientNetworkInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric.network; 2 | 3 | import io.github.kosmx.emotes.arch.network.NetworkPlatformTools; 4 | import io.github.kosmx.emotes.arch.network.client.ClientNetwork; 5 | import io.github.kosmx.emotes.common.CommonData; 6 | import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; 7 | import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; 8 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; 9 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 10 | 11 | import java.io.IOException; 12 | 13 | public class ClientNetworkInstance { 14 | @SuppressWarnings("deprecation") 15 | public static void init() { 16 | // Configuration 17 | 18 | ClientConfigurationNetworking.registerGlobalReceiver(NetworkPlatformTools.EMOTE_CHANNEL_ID, (buf, context) -> { 19 | try { 20 | ClientNetwork.INSTANCE.receiveConfigMessage(buf.packet(), context.responseSender()::sendPacket); 21 | } catch (IOException e) { 22 | CommonData.LOGGER.error("", e); 23 | } 24 | }); 25 | 26 | ClientConfigurationNetworking.registerGlobalReceiver(NetworkPlatformTools.STREAM_CHANNEL_ID, (buf, context) -> { 27 | try { 28 | ClientNetwork.INSTANCE.receiveStreamMessage(buf.packet(), context.responseSender()::sendPacket); 29 | } catch (IOException e) { 30 | CommonData.LOGGER.error("", e); 31 | } 32 | }); 33 | 34 | // Play 35 | C2SPlayChannelEvents.REGISTER.register((handler, sender, minecraft, channels) -> { 36 | if (channels.contains(NetworkPlatformTools.EMOTE_CHANNEL_ID.id())) { 37 | ClientNetwork.INSTANCE.configureOnPlay(sender::sendPacket); 38 | } 39 | }); 40 | ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> ClientNetwork.INSTANCE.disconnect()); 41 | 42 | ClientPlayNetworking.registerGlobalReceiver(NetworkPlatformTools.EMOTE_CHANNEL_ID, 43 | (buf, context) -> ClientNetwork.INSTANCE.receiveMessage(buf.packet()) 44 | ); 45 | 46 | ClientPlayNetworking.registerGlobalReceiver(NetworkPlatformTools.STREAM_CHANNEL_ID, (buf, context) -> { 47 | try { 48 | ClientNetwork.INSTANCE.receiveStreamMessage(buf.packet(), context.responseSender()::sendPacket); 49 | } catch (IOException e) { 50 | CommonData.LOGGER.error("", e); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /emotesAPI/src/main/java/io/github/kosmx/emotes/common/CommonData.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.common; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * static channel to access constant from everywhere in the mod. 8 | * Including Fabric and Bukkit code. 9 | */ 10 | public class CommonData { 11 | public static final String MOD_ID = "emotecraft"; 12 | public static final String MOD_NAME = "Emotecraft"; 13 | public static final Logger LOGGER = LoggerFactory.getLogger(MOD_NAME); 14 | 15 | /** 16 | * ver 1: older versions 17 | * ver 2: no network discovery, repeating and bending exists 18 | * ver 3: network discovery 19 | * ver 4: not syncing head bending values 20 | * ver 5: boolean, easing can indicated after the move 21 | * ver 6: experimental sound sync 22 | *

23 | * ver 7: reserved 24 | *

25 | * -------------------------------------- 26 | *

27 | * ver 8: New networking: 28 | * sub packet versioning, Collar network ready, sync current tick instead of repeat boolean 29 | * EmoteUUID in play and stop, no spamming, Eases in bytecodes instead of Strings 30 | * Only one network ID, not compatibly with earlier versions. 31 | * sub-packets and sub-versions. probably final version... 32 | */ 33 | public static final byte networkingVersion = 8; 34 | 35 | /** 36 | * bidirectional, Emote playing or repeating 37 | * Channel for common Emotecraft networking. 38 | */ 39 | public static final String playEmoteID = "emote"; 40 | 41 | /** 42 | * Wrapper type for huge blobs. used for emote sync (since 2.2) 43 | * Data sent here is going to be unwrapped and sent to playEmote channel handler. 44 | *

45 | * Servers may completely ignore this channel in play state. 46 | * see {@link io.github.kosmx.emotes.common.network.PacketConfig} 47 | */ 48 | public static final String emoteStreamID = "stream"; 49 | 50 | /** 51 | * Maximum packet size that can be sent by the client/received by the server 52 | * Could have been even bigger, but the paper servers sucks 53 | *

54 | * Identical to ClientboundCustomPayloadPacket#MAX_PAYLOAD_SIZE 55 | */ 56 | public static final int MAX_PACKET_SIZE = 1048576; 57 | 58 | ////bidirectional, Emote stop request 59 | //public static final String stopEmoteID = "stopemote"; 60 | ////bidirectional, client-server version exchange 61 | //public static final String discoverEmoteID = "discovery"; 62 | 63 | public static String getIDAsString(String channel){ 64 | return MOD_ID + ":" + channel; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/type/impl/BinaryFormat.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer.type.impl; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import io.github.kosmx.emotes.common.network.EmotePacket; 5 | import io.github.kosmx.emotes.common.network.PacketTask; 6 | import io.github.kosmx.emotes.common.network.objects.NetData; 7 | import io.github.kosmx.emotes.server.serializer.type.EmoteSerializerException; 8 | import io.github.kosmx.emotes.server.serializer.type.IReader; 9 | import io.github.kosmx.emotes.server.serializer.type.IWriter; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.ByteBufAllocator; 12 | import io.netty.buffer.PooledByteBufAllocator; 13 | 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | import java.util.Collections; 17 | import java.util.Map; 18 | 19 | public class BinaryFormat implements IReader, IWriter { 20 | private static final ByteBufAllocator ALLOC = PooledByteBufAllocator.DEFAULT; 21 | 22 | @Override 23 | public Map read(InputStream stream, String filename) throws EmoteSerializerException { 24 | ByteBuf buf = ALLOC.buffer(); 25 | try { 26 | buf.writeBytes(stream.readAllBytes()); 27 | 28 | NetData data = new EmotePacket(buf).data; 29 | if (data.purpose != PacketTask.FILE || data.emoteData == null) { 30 | throw new EmoteSerializerException("Binary emote is invalid", getExtension()); 31 | } 32 | return Collections.singletonMap(data.emoteData.getNameOrId(), data.emoteData); 33 | } catch (Throwable exception) { 34 | throw new EmoteSerializerException("Something went wrong", getExtension(), exception); 35 | } finally { 36 | buf.release(); 37 | } 38 | } 39 | 40 | @Override 41 | public void write(Animation emote, OutputStream stream, String filename) throws EmoteSerializerException { 42 | ByteBuf buf = ALLOC.buffer(); 43 | try { 44 | new EmotePacket.Builder().strictSizeLimit(false).configureToSaveEmote(emote).build().write(buf, ALLOC); 45 | buf.readBytes(stream, buf.readableBytes()); 46 | } catch (Throwable e){ 47 | throw new EmoteSerializerException("Something went wrong", getExtension(), e); 48 | } finally { 49 | buf.release(); 50 | } 51 | } 52 | 53 | @Override 54 | public boolean onlyEmoteFile() { 55 | return false; 56 | } 57 | 58 | @Override 59 | public boolean possibleDataLoss() { 60 | return false; 61 | } 62 | 63 | @Override 64 | public String getExtension() { 65 | return "emotecraft"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /minecraft/neoforge/src/main/java/io/github/kosmx/emotes/neoforge/EmotecraftClientNeoMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.neoforge; 2 | 3 | import io.github.kosmx.emotes.arch.ClientCommands; 4 | import io.github.kosmx.emotes.arch.EmotecraftClientMod; 5 | import io.github.kosmx.emotes.arch.network.client.ClientNetwork; 6 | import io.github.kosmx.emotes.arch.screen.EmoteMenu; 7 | import io.github.kosmx.emotes.common.CommonData; 8 | import net.minecraft.client.Minecraft; 9 | import net.neoforged.api.distmarker.Dist; 10 | import net.neoforged.bus.api.IEventBus; 11 | import net.neoforged.bus.api.SubscribeEvent; 12 | import net.neoforged.fml.ModContainer; 13 | import net.neoforged.fml.common.Mod; 14 | import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; 15 | import net.neoforged.neoforge.client.event.ClientTickEvent; 16 | import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; 17 | import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; 18 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 19 | import net.neoforged.neoforge.client.network.ClientPacketDistributor; 20 | import net.neoforged.neoforge.common.NeoForge; 21 | 22 | @Mod(value = CommonData.MOD_ID, dist = Dist.CLIENT) 23 | public class EmotecraftClientNeoMod extends EmotecraftClientMod { 24 | 25 | public EmotecraftClientNeoMod(ModContainer container, IEventBus modEventBus) { 26 | container.registerExtensionPoint(IConfigScreenFactory.class, (minecraft, screen) -> new EmoteMenu(screen)); 27 | super.onInitializeClient(); 28 | 29 | NeoForge.EVENT_BUS.addListener(this::onClientTickPost); 30 | NeoForge.EVENT_BUS.addListener(this::onLoggingOut); 31 | NeoForge.EVENT_BUS.addListener(this::onLoggingIn); 32 | modEventBus.addListener(this::onRegisterKeyMappings); 33 | } 34 | 35 | @SubscribeEvent 36 | public void onClientTickPost(ClientTickEvent.Post event) { 37 | super.onClientTick(Minecraft.getInstance()); 38 | } 39 | 40 | @SubscribeEvent 41 | public void onLoggingOut(ClientPlayerNetworkEvent.LoggingOut event) { 42 | ClientNetwork.INSTANCE.disconnect(); 43 | } 44 | 45 | @SubscribeEvent 46 | @SuppressWarnings("deprecation") 47 | public void onLoggingIn(ClientPlayerNetworkEvent.LoggingIn event) { 48 | ClientNetwork.INSTANCE.configureOnPlay(ClientPacketDistributor::sendToServer); 49 | } 50 | 51 | public void onRegisterKeyMappings(RegisterKeyMappingsEvent event) { 52 | event.register(OPEN_MENU_KEY); 53 | event.register(STOP_EMOTE_KEY); 54 | } 55 | 56 | @SubscribeEvent 57 | public void onRegisterClientCommands(RegisterClientCommandsEvent event) { 58 | ClientCommands.register(event.getDispatcher(), event.getBuildContext()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/EmotecraftClientMod.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import io.github.kosmx.emotes.PlatformTools; 5 | import io.github.kosmx.emotes.arch.screen.ingame.FastMenuScreen; 6 | import io.github.kosmx.emotes.common.CommonData; 7 | import io.github.kosmx.emotes.main.EmoteHolder; 8 | import io.github.kosmx.emotes.main.network.ClientEmotePlay; 9 | import io.github.kosmx.emotes.main.network.ClientPacketManager; 10 | import io.github.kosmx.emotes.mc.McUtils; 11 | import io.github.kosmx.emotes.server.serializer.UniversalEmoteSerializer; 12 | import net.minecraft.client.KeyMapping; 13 | import net.minecraft.client.Minecraft; 14 | import net.minecraft.util.Util; 15 | import org.lwjgl.glfw.GLFW; 16 | 17 | import java.util.concurrent.CompletableFuture; 18 | 19 | public class EmotecraftClientMod { 20 | protected static final KeyMapping.Category KEYBIND_CATEGORY = KeyMapping.Category.register(McUtils.newIdentifier("keybinding")); // key.category.emotecraft.keybinding 21 | 22 | public static final KeyMapping OPEN_MENU_KEY = new KeyMapping( 23 | "key.emotecraft.fastchoose", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_B, KEYBIND_CATEGORY 24 | ); 25 | public static final KeyMapping STOP_EMOTE_KEY = new KeyMapping( 26 | "key.emotecraft.stop", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, KEYBIND_CATEGORY 27 | ); 28 | 29 | private static int tick = 0; 30 | 31 | protected void onInitializeClient() { 32 | EmotecraftClientMod.loadEmotes(); 33 | ClientPacketManager.init(); // initialize proxy service 34 | } 35 | 36 | protected void onClientTick(Minecraft minecraft) { 37 | if (tick++ % 21 == 20) ClientEmotePlay.checkQueue(); 38 | 39 | if (OPEN_MENU_KEY.consumeClick()) { 40 | if(PlatformTools.getConfig().alwaysOpenEmoteScreen.get() || minecraft.player == minecraft.getCameraEntity()) { 41 | minecraft.setScreen(new FastMenuScreen(null)); 42 | } 43 | } 44 | 45 | if (STOP_EMOTE_KEY.consumeClick()) { 46 | ClientEmotePlay.clientStopLocalEmote(); 47 | } 48 | } 49 | 50 | public static CompletableFuture loadEmotes() { 51 | return CompletableFuture.supplyAsync(UniversalEmoteSerializer::loadEmotes, Util.ioPool()) 52 | .thenAccept(emotes -> { 53 | EmoteHolder.clearEmotes(); 54 | EmoteHolder.addEmoteToList(UniversalEmoteSerializer.getLoadedEmotes(), null); 55 | }) 56 | .exceptionally(th -> { 57 | CommonData.LOGGER.error("Failed to reload emotes!", th); 58 | return null; 59 | }); 60 | } 61 | 62 | public static int getTick() { 63 | return tick; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /paper/src/main/java/io/github/kosmx/emotes/bukkit/fuckery/StreamCodecUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.bukkit.fuckery; 2 | 3 | import io.github.kosmx.emotes.common.CommonData; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.util.internal.shaded.org.jctools.util.UnsafeAccess; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.Packet; 8 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 9 | 10 | import java.lang.invoke.MethodHandles; 11 | import java.lang.invoke.VarHandle; 12 | import java.lang.reflect.Field; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | @SuppressWarnings({"unchecked", "deprecation"}) 17 | public class StreamCodecUtils { 18 | protected static final MethodHandles.Lookup TRUSTED_LOOKUP; 19 | 20 | static { 21 | try { 22 | Field hackfield = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); 23 | TRUSTED_LOOKUP = (MethodHandles.Lookup) UnsafeAccess.UNSAFE.getObject( 24 | UnsafeAccess.UNSAFE.staticFieldBase(hackfield), 25 | UnsafeAccess.UNSAFE.staticFieldOffset(hackfield) 26 | ); 27 | } catch (ReflectiveOperationException ex) { 28 | throw new RuntimeException(ex); 29 | } 30 | } 31 | 32 | protected static final List FALLBACK_FIELDS = Arrays.asList("val$fallback", "val$fallbackProvider"); 33 | 34 | public static void replaceFallback(StreamCodec> codec, CustomPacketPayload.FallbackProvider provider) throws ReflectiveOperationException { 35 | ReflectiveOperationException exception = null; 36 | for (String fallbackField : FALLBACK_FIELDS) { 37 | try { 38 | VarHandle varHandle = TRUSTED_LOOKUP.findVarHandle(codec.getClass(), fallbackField, CustomPacketPayload.FallbackProvider.class); 39 | varHandle.set(codec, provider); 40 | return; 41 | } catch (ReflectiveOperationException ex) { 42 | exception = ex; 43 | } 44 | } 45 | if (exception != null) { 46 | CommonData.LOGGER.info(Arrays.toString(codec.getClass().getDeclaredFields())); 47 | throw exception; 48 | } 49 | } 50 | 51 | public static StreamCodec> getThis(StreamCodec> codec) throws ReflectiveOperationException { 52 | try { 53 | VarHandle varHandle = TRUSTED_LOOKUP.findVarHandle(codec.getClass(), "this$0", StreamCodec.class); 54 | return (StreamCodec>) varHandle.get(codec); 55 | } catch (ReflectiveOperationException ex) { 56 | CommonData.LOGGER.info(Arrays.toString(codec.getClass().getDeclaredFields())); 57 | throw ex; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/config/ConfigSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.config; 2 | 3 | import com.google.gson.*; 4 | import io.github.kosmx.emotes.common.CommonData; 5 | import io.github.kosmx.emotes.common.SerializableConfig; 6 | 7 | import java.lang.reflect.Type; 8 | import java.util.function.Supplier; 9 | 10 | public class ConfigSerializer implements JsonDeserializer, JsonSerializer { 11 | protected final Supplier configSuppler; 12 | 13 | public ConfigSerializer(Supplier configSuppler) { 14 | this.configSuppler = configSuppler; 15 | } 16 | 17 | @Override 18 | public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException{ 19 | JsonObject node = json.getAsJsonObject(); 20 | T config = this.configSuppler.get(); 21 | config.configVersion = SerializableConfig.staticConfigVersion; 22 | if (node.has("config_version")) 23 | config.configVersion = node.get("config_version").getAsInt(); 24 | 25 | if (config.configVersion < SerializableConfig.staticConfigVersion) { 26 | CommonData.LOGGER.debug("Serializing config with older version..."); 27 | 28 | } else if (config.configVersion > SerializableConfig.staticConfigVersion) { 29 | CommonData.LOGGER.warn("You are trying to load version {} config. The mod can only load correctly up to {}. If you won't modify any config, I won't overwrite your config file.", config.configVersion, SerializableConfig.staticConfigVersion); 30 | } 31 | 32 | config.iterate(entry -> deserializeEntry(entry, node, context)); 33 | 34 | return config; 35 | } 36 | 37 | protected void deserializeEntry(SerializableConfig.ConfigEntry entry, JsonObject node, JsonDeserializationContext context) { 38 | String id = null; 39 | if (node.has(entry.getName())) { 40 | id = entry.getName(); 41 | 42 | } else if (node.has(entry.getOldConfigName())) { 43 | id = entry.getOldConfigName(); 44 | } 45 | 46 | if (id == null) 47 | return; 48 | 49 | entry.set(context.deserialize(node.get(id), entry.get().getClass())); 50 | } 51 | 52 | @Override 53 | public JsonElement serialize(SerializableConfig config, Type typeOfSrc, JsonSerializationContext context) { 54 | JsonObject node = new JsonObject(); 55 | node.addProperty("config_version", SerializableConfig.staticConfigVersion); //I always save config with the latest format. 56 | config.iterate(entry -> serializeEntry(entry, node, context)); 57 | return node; 58 | } 59 | 60 | protected void serializeEntry(SerializableConfig.ConfigEntry entry, JsonObject node, JsonSerializationContext context) { 61 | node.add(entry.getName(), context.serialize(entry.get())); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/emotePlay/instances/SoundDirectInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.emotePlay.instances; 2 | 3 | import net.minecraft.client.resources.sounds.Sound; 4 | import net.minecraft.client.resources.sounds.SoundInstance; 5 | import net.minecraft.client.sounds.SoundManager; 6 | import net.minecraft.client.sounds.WeighedSoundEvents; 7 | import net.minecraft.resources.Identifier; 8 | import net.minecraft.sounds.SoundSource; 9 | import net.minecraft.util.valueproviders.ConstantFloat; 10 | import net.minecraft.util.valueproviders.FloatProvider; 11 | import net.minecraft.world.phys.Vec3; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class SoundDirectInstance implements SoundInstance { 16 | private static final FloatProvider DEFAULT_FLOAT = ConstantFloat.of(1.0F); 17 | 18 | protected final Sound sound; 19 | protected final float volume; 20 | protected final float pitch; 21 | protected final Vec3 pos; 22 | 23 | public SoundDirectInstance(Identifier sound, float volume, float pitch, Vec3 pos) { 24 | this(new Sound(sound, DEFAULT_FLOAT, DEFAULT_FLOAT, 1, Sound.Type.FILE, false, false, 16), volume, pitch, pos); 25 | } 26 | 27 | public SoundDirectInstance(Sound sound, float volume, float pitch, Vec3 pos) { 28 | this.sound = sound; 29 | this.volume = volume; 30 | this.pitch = pitch; 31 | this.pos = pos; 32 | } 33 | 34 | @Override 35 | public @NotNull Identifier getIdentifier() { 36 | return this.sound.getLocation(); 37 | } 38 | 39 | @Override 40 | public @Nullable WeighedSoundEvents resolve(SoundManager manager) { 41 | return new EmotecraftSoundEvents(this.sound); 42 | } 43 | 44 | @Override 45 | public @NotNull Sound getSound() { 46 | return this.sound; 47 | } 48 | 49 | @Override 50 | public @NotNull SoundSource getSource() { 51 | return SoundSource.PLAYERS; 52 | } 53 | 54 | @Override 55 | public boolean isLooping() { 56 | return false; 57 | } 58 | 59 | @Override 60 | public boolean isRelative() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public int getDelay() { 66 | return 0; 67 | } 68 | 69 | @Override 70 | public float getVolume() { 71 | return this.volume; 72 | } 73 | 74 | @Override 75 | public float getPitch() { 76 | return this.pitch; 77 | } 78 | 79 | @Override 80 | public double getX() { 81 | return this.pos.x() + 0.5; 82 | } 83 | 84 | @Override 85 | public double getY() { 86 | return this.pos.y() + 0.5; 87 | } 88 | 89 | @Override 90 | public double getZ() { 91 | return this.pos.z() + 0.5; 92 | } 93 | 94 | @Override 95 | public @NotNull Attenuation getAttenuation() { 96 | return Attenuation.LINEAR; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ dev ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ dev ] 20 | #schedule: 21 | # - cron: '21 23 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] #Don't need to waste time on python dev tools 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v6 43 | 44 | - name: Setup Java JDK 45 | uses: actions/setup-java@v5 46 | with: 47 | distribution: 'microsoft' 48 | java-version: '21' 49 | cache: 'gradle' 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@v4 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 60 | 61 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 62 | # If this step fails, then you should remove it and run the build manually (see below) 63 | - name: Autobuild 64 | uses: github/codeql-action/autobuild@v4 65 | 66 | # ℹ️ Command-line programs to run using the OS shell. 67 | # 📚 https://git.io/JvXDl 68 | 69 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 70 | # and modify them (or add more) to build your code if your project 71 | # uses a compiled language 72 | 73 | #- run: | 74 | # make bootstrap 75 | # make release 76 | 77 | - name: Perform CodeQL Analysis 78 | uses: github/codeql-action/analyze@v4 79 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/ClientCommands.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import com.mojang.brigadier.arguments.StringArgumentType; 5 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 6 | import com.mojang.brigadier.context.CommandContext; 7 | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; 8 | import com.zigythebird.playeranimcore.animation.Animation; 9 | import io.github.kosmx.emotes.main.EmoteHolder; 10 | import io.github.kosmx.emotes.main.network.ClientEmotePlay; 11 | import io.github.kosmx.emotes.mc.EmoteArgumentProvider; 12 | import net.minecraft.commands.CommandBuildContext; 13 | import net.minecraft.commands.CommandSourceStack; 14 | import net.minecraft.network.chat.Component; 15 | 16 | import java.util.Map; 17 | import java.util.UUID; 18 | import java.util.stream.Collectors; 19 | 20 | import static net.minecraft.commands.Commands.argument; 21 | import static net.minecraft.commands.Commands.literal; 22 | 23 | /** 24 | * Client-side commands, no permission verification, we're on the client 25 | */ 26 | public class ClientCommands { 27 | public static final Component FORCED = Component.translatable("emotecraft.cant.override.forced"); 28 | 29 | @SuppressWarnings({"unchecked","unused"}) 30 | public static void register(CommandDispatcher dispatcher, CommandBuildContext registryAccess) { 31 | dispatcher.register((LiteralArgumentBuilder) literal("emotes-client") 32 | .then(literal("play") 33 | .then(argument("emote", StringArgumentType.string()).suggests(new EmoteArgumentProvider(ClientCommands::getEmotes)) 34 | .executes(ctx -> { 35 | if (!ClientEmotePlay.clientStartLocalEmote(EmoteArgumentProvider.getEmote(getEmotes(ctx), ctx, "emote"))) { 36 | throw new SimpleCommandExceptionType(FORCED).create(); 37 | } 38 | return 0; 39 | }) 40 | ) 41 | ) 42 | .then(literal("stop") 43 | .executes(ctx -> { 44 | if (ClientEmotePlay.isForcedEmote()) 45 | throw new SimpleCommandExceptionType(FORCED).create(); 46 | ClientEmotePlay.clientStopLocalEmote(); 47 | return 0; 48 | } 49 | ) 50 | ) 51 | ); 52 | } 53 | 54 | private static Map getEmotes(CommandContext context) { 55 | return EmoteHolder.list.entrySet().stream().collect(Collectors.toMap( 56 | Map.Entry::getKey, entry -> entry.getValue().emote 57 | )); 58 | } 59 | } -------------------------------------------------------------------------------- /minecraft/fabric/src/main/java/io/github/kosmx/emotes/fabric/network/ServerNetworkStuff.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.fabric.network; 2 | 3 | import io.github.kosmx.emotes.arch.mixin.ServerCommonPacketListenerAccessor; 4 | import io.github.kosmx.emotes.arch.network.*; 5 | import io.github.kosmx.emotes.common.CommonData; 6 | import io.github.kosmx.emotes.common.network.PacketTask; 7 | import io.github.kosmx.emotes.server.serializer.UniversalEmoteSerializer; 8 | import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; 9 | import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; 10 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 11 | import net.minecraft.network.chat.Component; 12 | 13 | import java.io.IOException; 14 | 15 | public final class ServerNetworkStuff { 16 | public static void init() { 17 | PayloadTypeRegistator.init(); 18 | 19 | // Config networking 20 | 21 | ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { 22 | if (ServerConfigurationNetworking.canSend(handler, NetworkPlatformTools.EMOTE_CHANNEL_ID)) { 23 | handler.addTask(new ConfigTask()); 24 | } else { // No disconnect, vanilla clients can connect 25 | CommonData.LOGGER.debug("Client doesn't support emotes, ignoring"); 26 | } 27 | }); 28 | 29 | ServerConfigurationNetworking.registerGlobalReceiver(NetworkPlatformTools.EMOTE_CHANNEL_ID, (payload, context) -> { 30 | try { 31 | var message = payload.packet().data; 32 | if (message.purpose != PacketTask.CONFIG) throw new IOException("Wrong packet type for config task"); 33 | 34 | ((EmotesMixinConnection) ((ServerCommonPacketListenerAccessor) context.networkHandler()).getConnection()).emotecraft$setVersions(message.versions); 35 | UniversalEmoteSerializer.preparePackets(message.versions) 36 | .map(EmotePacketPayload::playPacket) 37 | .forEach(context.responseSender()::sendPacket); 38 | 39 | context.networkHandler().completeTask(ConfigTask.TYPE); // And, we're done here 40 | } catch (IOException e) { 41 | CommonData.LOGGER.error("", e); 42 | context.networkHandler().disconnect(Component.literal(CommonData.MOD_ID + ": " + e.getMessage())); 43 | } 44 | }); 45 | 46 | // Play networking 47 | ServerPlayNetworking.registerGlobalReceiver(NetworkPlatformTools.EMOTE_CHANNEL_ID, (buf, context) -> 48 | CommonServerNetworkHandler.getInstance().receiveMessage(buf.packet(), context.player()) 49 | ); 50 | ServerPlayNetworking.registerGlobalReceiver(NetworkPlatformTools.STREAM_CHANNEL_ID, (buf, context) -> 51 | CommonServerNetworkHandler.getInstance().receiveStreamMessage(buf.packet(), context.player()) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /emotesAssets/src/main/resources/assets/emotecraft/emotes/waving.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "translate": "emotecraft.emote.waving.name" 4 | }, 5 | "author": { 6 | "player": { 7 | "id": [2143116393, -1362800877, -2082782478, 1380574660] 8 | }, 9 | "extra": [ 10 | " §7KosmX" 11 | ], 12 | "color": "#ffffff" 13 | }, 14 | "description": { 15 | "translate": "emotecraft.emote.waving.description" 16 | }, 17 | "uuid": "33b912f8-0aa0-45e6-a2d4-9b5677e6f35c", 18 | "emote":{ 19 | "beginTick":12, 20 | "endTick":45, 21 | "stopTick":50, 22 | "degrees":true, 23 | "previewTick": 10, 24 | "moves":[ 25 | { 26 | "tick":0, 27 | "easing": "inQuad", 28 | "torso":{ 29 | "roll":0 30 | }, 31 | "rightLeg":{ 32 | "roll":0, 33 | "x": -1.9, 34 | "y": 12 35 | }, 36 | "leftLeg":{ 37 | "roll":0, 38 | "x": 1.9, 39 | "y": 12 40 | } 41 | }, 42 | { 43 | "tick":8, 44 | "easing": "InOutSine", 45 | "rightArm":{ 46 | "yaw":90 47 | } 48 | }, 49 | { 50 | "tick":10, 51 | "easing": "InOutSine", 52 | "rightArm":{ 53 | "roll":185 54 | }, 55 | "leftArm":{ 56 | "roll": -5 57 | } 58 | }, 59 | { 60 | "tick":10, 61 | "easing": "outExpo", 62 | "torso":{ 63 | "roll":8 64 | }, 65 | "rightLeg":{ 66 | "roll":-8, 67 | "x": -1.9, 68 | "y": 12.25 69 | }, 70 | "leftLeg":{ 71 | "roll":-8, 72 | "x": 1.9, 73 | "y": 11.75 74 | } 75 | }, 76 | { 77 | "tick": 15, 78 | "easing": "InOutSine", 79 | "rightArm":{ 80 | "roll":95 81 | } 82 | }, 83 | { 84 | "tick": 20, 85 | "easing": "InOutSine", 86 | "rightArm":{ 87 | "roll":185 88 | } 89 | }, 90 | { 91 | "tick": 25, 92 | "easing": "InOutSine", 93 | "rightArm":{ 94 | "roll":95 95 | } 96 | }, 97 | { 98 | "tick": 30, 99 | "easing": "InOutSine", 100 | "rightArm":{ 101 | "roll":185 102 | } 103 | }, 104 | { 105 | "tick": 35, 106 | "easing": "InOutSine", 107 | "rightArm":{ 108 | "roll":95 109 | } 110 | }, 111 | { 112 | "tick": 40, 113 | "easing": "InOutSine", 114 | "rightArm":{ 115 | "roll":185 116 | } 117 | }, 118 | { 119 | "tick":46, 120 | "easing": "InOutSine", 121 | "torso":{ 122 | "roll":11 123 | }, 124 | "rightLeg":{ 125 | "roll":-11, 126 | "x": -1.9, 127 | "y": 12.3 128 | }, 129 | "leftLeg":{ 130 | "roll":-11, 131 | "x": 1.9, 132 | "y": 11.7 133 | } 134 | }, 135 | { 136 | "tick": 48, 137 | "easing": "inoutcirc", 138 | "leftArm":{ 139 | "roll":-17 140 | } 141 | }, 142 | { 143 | "tick":48, 144 | "easing": "InOutSine", 145 | "rightArm":{ 146 | "yaw":90 147 | } 148 | } 149 | ] 150 | } 151 | } -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/utils/PageButton.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.utils; 2 | 3 | import net.minecraft.client.gui.GuiGraphics; 4 | import net.minecraft.client.gui.components.AbstractButton; 5 | import net.minecraft.client.gui.components.WidgetSprites; 6 | import net.minecraft.client.gui.narration.NarrationElementOutput; 7 | import net.minecraft.client.input.InputWithModifiers; 8 | import net.minecraft.client.renderer.RenderPipelines; 9 | import net.minecraft.network.chat.CommonComponents; 10 | import net.minecraft.util.ARGB; 11 | 12 | import java.util.function.Consumer; 13 | 14 | /** 15 | * Merged version of {@link net.minecraft.client.gui.components.StateSwitchingButton} and {@link AbstractButton} into one class for convenient usage. 16 | */ 17 | public class PageButton extends AbstractButton { 18 | public static final int PAGE_BUTTON_WIDTH = 12; 19 | public static final int PAGE_BUTTON_HEIGHT = 17; 20 | 21 | protected final WidgetSprites sprites; 22 | protected final boolean drawBackground; 23 | protected final Consumer onPress; 24 | 25 | public PageButton(WidgetSprites sprites, boolean background, Consumer onPress) { 26 | this(PAGE_BUTTON_WIDTH, PAGE_BUTTON_HEIGHT, sprites, background, onPress); 27 | } 28 | 29 | public PageButton(int width, int height, WidgetSprites sprites, boolean background, Consumer onPress) { 30 | this(0, 0, width, height, sprites, background, onPress); 31 | } 32 | 33 | public PageButton(int x, int y, int width, int height, WidgetSprites sprites, boolean background, Consumer onPress) { 34 | super(x, y, width, height, CommonComponents.EMPTY); 35 | this.sprites = sprites; 36 | this.drawBackground = background; 37 | this.onPress = onPress; 38 | } 39 | 40 | @Override 41 | protected void renderContents(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { 42 | if (this.drawBackground) super.renderDefaultSprite(guiGraphics); 43 | 44 | int width = this.drawBackground ? PAGE_BUTTON_WIDTH : getWidth(); 45 | int height = this.drawBackground ? PAGE_BUTTON_HEIGHT : getHeight(); 46 | 47 | int x = getX(); 48 | int y = getY(); 49 | 50 | if (this.drawBackground) { 51 | x += (getWidth() - width) / 2; 52 | y += (getHeight() - height) / 2; 53 | } 54 | 55 | guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, this.sprites.get(this.active, this.active && isHoveredOrFocused()), x, y, 56 | width, height, ARGB.white(this.alpha) 57 | ); 58 | } 59 | 60 | @Override 61 | public void onPress(InputWithModifiers inputWithModifiers) { 62 | this.onPress.accept(this); 63 | } 64 | 65 | @Override 66 | protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { 67 | defaultButtonNarrationText(narrationElementOutput); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/utils/UnsafeMannequin.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.utils; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.entity.ClientMannequin; 6 | import net.minecraft.client.multiplayer.ClientLevel; 7 | import net.minecraft.client.player.LocalPlayer; 8 | import net.minecraft.tags.TagKey; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.entity.player.PlayerModelPart; 11 | import net.minecraft.world.item.component.ResolvableProfile; 12 | import net.minecraft.world.level.block.Blocks; 13 | import net.minecraft.world.level.block.state.BlockState; 14 | import net.minecraft.world.level.material.Fluid; 15 | import net.minecraft.world.phys.Vec3; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.Objects; 20 | 21 | public class UnsafeMannequin extends ClientMannequin { 22 | public UnsafeMannequin(@Nullable ClientLevel clientLevel, GameProfile gameProfile) { 23 | super(Objects.requireNonNullElse(clientLevel, UnsafeClientLevel.INSTANCE), Minecraft.getInstance().playerSkinRenderCache()); 24 | setProfile(ResolvableProfile.createResolved(gameProfile)); 25 | setHideDescription(true); 26 | } 27 | 28 | @Override 29 | public boolean isInvisibleTo(Player player) { 30 | return true; 31 | } 32 | 33 | @Override 34 | public boolean isModelPartShown(PlayerModelPart part) { 35 | return true; 36 | } 37 | 38 | @Override 39 | public void initEmotePerspective() { 40 | // no-op 41 | } 42 | 43 | @Override 44 | protected void updateSkin() { 45 | super.updateSkin(); 46 | if (this.skinLookup != null) { 47 | this.skinLookup.thenAccept(playerSkin -> playerSkin.ifPresent(this::setSkin)); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean touchingUnloadedChunk() { 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean updateFluidHeightAndDoFluidPushing(TagKey fluidTag, double motionScale) { 58 | return false; 59 | } 60 | 61 | @Override 62 | protected boolean updateInWaterStateAndDoFluidPushing() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public void baseTick() { 68 | tickCount++; 69 | } 70 | 71 | @Override 72 | public void aiStep() { 73 | // no-op 74 | } 75 | 76 | @Override 77 | public boolean isCustomNameVisible() { 78 | return false; 79 | } 80 | 81 | @Override 82 | public @NotNull BlockState getInBlockState() { 83 | return Blocks.VOID_AIR.defaultBlockState(); 84 | } 85 | 86 | @Override 87 | public @NotNull Vec3 position() { 88 | LocalPlayer localPlayer = Minecraft.getInstance().player; 89 | if (localPlayer == null) return super.position(); 90 | return localPlayer.position(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/main/mixinFunctions/IPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.main.mixinFunctions; 2 | 3 | import com.zigythebird.playeranim.util.ClientUtil; 4 | import com.zigythebird.playeranimcore.animation.Animation; 5 | import io.github.kosmx.emotes.PlatformTools; 6 | import io.github.kosmx.emotes.main.emotePlay.EmotePlayer; 7 | import io.github.kosmx.emotes.main.network.ClientEmotePlay; 8 | 9 | import net.minecraft.client.CameraType; 10 | import net.minecraft.client.Minecraft; 11 | import net.minecraft.client.resources.sounds.SoundInstance; 12 | import org.apache.commons.lang3.NotImplementedException; 13 | import org.jetbrains.annotations.ApiStatus; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.UUID; 18 | 19 | public interface IPlayerEntity { 20 | default void initEmotePerspective() { 21 | if (isMainAvatar() && PlatformTools.getConfig().enablePerspective.get() && PlatformTools.getPerspective() == CameraType.FIRST_PERSON) { 22 | emotecraft$getEmote().perspective = true; 23 | PlatformTools.setPerspective(PlatformTools.getConfig().getCameraType()); 24 | } 25 | } 26 | 27 | default void emotecraft$playEmote(@Nullable Animation emote, float tick, boolean isForced) { 28 | emotecraft$playEmote(emote, Animation.LoopType.DEFAULT, tick, isForced); 29 | } 30 | 31 | @ApiStatus.Internal 32 | default void emotecraft$playEmote(@Nullable Animation emote, Animation.LoopType loopType, float tick, boolean isForced) { 33 | throw new NotImplementedException(); 34 | } 35 | 36 | default @NotNull EmotePlayer emotecraft$getEmote() { 37 | throw new NotImplementedException(); 38 | } 39 | 40 | default boolean isPlayingEmote() { 41 | return EmotePlayer.isRunningEmote(this.emotecraft$getEmote()); 42 | } 43 | 44 | default boolean isMainAvatar() { 45 | return ClientUtil.getClientPlayer() == this; 46 | } 47 | 48 | /** 49 | * Use this ONLY for the main player 50 | */ 51 | default void stopEmote() { 52 | emotecraft$getEmote().stop(); 53 | } 54 | 55 | default void stopEmote(UUID emoteID) { 56 | Animation animation = emotecraft$getEmote().getCurrentAnimationInstance(); 57 | if (animation != null &&animation.uuid().equals(emoteID)) { 58 | stopEmote(); 59 | } 60 | } 61 | 62 | default boolean emotecraft$isForcedEmote() { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | default void emotecraft$playerEntersInvalidPose() { 67 | if (!isPlayingEmote() || emotecraft$isForcedEmote()) { 68 | return; 69 | } 70 | 71 | if (PlatformTools.getConfig().checkPose.get()) { 72 | ClientEmotePlay.clientStopLocalEmote(emotecraft$getEmote().getCurrentAnimationInstance()); 73 | } 74 | } 75 | 76 | default void emotecraft$playRawSound(SoundInstance instance) { 77 | Minecraft.getInstance().getSoundManager().play(instance); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /emotesServer/src/main/java/io/github/kosmx/emotes/server/serializer/EmoteWriter.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.server.serializer; 2 | 3 | import com.zigythebird.playeranimcore.animation.Animation; 4 | import io.github.kosmx.emotes.common.tools.MathHelper; 5 | import io.github.kosmx.emotes.server.serializer.type.IWriter; 6 | import net.raphimc.noteblocklib.NoteBlockLib; 7 | import net.raphimc.noteblocklib.model.Song; 8 | 9 | import java.io.OutputStream; 10 | import java.nio.ByteBuffer; 11 | import java.nio.file.FileAlreadyExistsException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.regex.Pattern; 15 | 16 | public class EmoteWriter { 17 | private static final Pattern INVALID_FILENAME_CHARS = Pattern.compile("[\\\\/:*?\"<>|]"); 18 | 19 | public static void writeAnimationInBestFormat(Animation animation, Path exportDir) throws Exception { 20 | writeAnimationInFormat(animation, exportDir, UniversalEmoteSerializer.findWriter(null)); 21 | } 22 | 23 | public static void writeAnimationInFormat(Animation animation, Path exportDir, IWriter format) throws Exception { 24 | Path file = createFileName(animation, exportDir, format.getExtension()); 25 | 26 | try (OutputStream stream = Files.newOutputStream(file)) { 27 | format.write(animation, stream, file.getFileName().toString()); 28 | } 29 | 30 | if (format.onlyEmoteFile()) { 31 | String fileName = EmoteSerializer.getBaseName(file.getFileName().toString()); 32 | 33 | if (animation.data().getRaw("iconData") instanceof ByteBuffer iconData) { 34 | Path iconPath = exportDir.resolve(fileName + ".png"); 35 | if (Files.exists(iconPath)) throw new FileAlreadyExistsException(iconPath.toString()); 36 | 37 | try (OutputStream iconStream = Files.newOutputStream(iconPath)) { 38 | iconStream.write(MathHelper.safeGetBytesFromBuffer(iconData)); 39 | iconStream.flush(); 40 | } 41 | } 42 | 43 | if (animation.data().getRaw("song") instanceof Song song) { 44 | Path songPath = exportDir.resolve(fileName + ".nbs"); 45 | if (Files.exists(songPath)) throw new FileAlreadyExistsException(songPath.toString()); 46 | NoteBlockLib.writeSong(song, songPath); 47 | } 48 | } 49 | } 50 | 51 | private static Path createFileName(Animation emote, Path originPath, String ext) { 52 | String fileName = emote.data().get(EmoteSerializer.FILENAME_KEY) 53 | .map(EmoteSerializer::getBaseName) 54 | .orElseGet(emote.data()::name); 55 | 56 | if (fileName == null) throw new NullPointerException(); 57 | String baseName = EmoteWriter.INVALID_FILENAME_CHARS.matcher(fileName).replaceAll("#"); 58 | Path file = originPath.resolve(baseName + "." + ext); 59 | 60 | int i = 1; 61 | while (Files.exists(file)) { 62 | file = originPath.resolve(baseName + "_" + i++ + "." + ext); 63 | } 64 | 65 | return file; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/ingame/FullMenuScreen.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.ingame; 2 | 3 | import io.github.kosmx.emotes.arch.gui.widgets.EmoteListWidget; 4 | import io.github.kosmx.emotes.arch.screen.EmoteMenu; 5 | import io.github.kosmx.emotes.arch.screen.components.EmoteSubScreen; 6 | import io.github.kosmx.emotes.main.EmoteHolder; 7 | import io.github.kosmx.emotes.main.emotePlay.EmotePlayer; 8 | import io.github.kosmx.emotes.main.mixinFunctions.IPlayerEntity; 9 | import net.minecraft.client.gui.GuiGraphics; 10 | import net.minecraft.client.gui.components.Button; 11 | import net.minecraft.client.gui.layouts.LinearLayout; 12 | import net.minecraft.client.gui.screens.Screen; 13 | import net.minecraft.network.chat.CommonComponents; 14 | import net.minecraft.network.chat.Component; 15 | 16 | public class FullMenuScreen extends EmoteSubScreen { 17 | protected static final Component TITLE = Component.translatable("emotecraft.emotelist"); 18 | protected static final Component CONFIG = Component.translatable("emotecraft.config"); 19 | 20 | public FullMenuScreen(Screen parent) { 21 | super(TITLE, false, parent); 22 | } 23 | 24 | @Override 25 | protected void addOptions() { 26 | this.list.setEmotes(EmoteHolder.list, false); 27 | } 28 | 29 | @Override 30 | protected void addFooter() { 31 | LinearLayout linearLayout = this.layout.addToFooter(LinearLayout.horizontal().spacing(Button.DEFAULT_SPACING)); 32 | 33 | if (this.list != null) linearLayout.addChild(this.list.createBackButton()); 34 | 35 | linearLayout.addChild(Button.builder(CommonComponents.GUI_CANCEL, button -> onClose()) 36 | .build() 37 | ); 38 | linearLayout.addChild(Button.builder(FullMenuScreen.CONFIG, button -> this.minecraft.setScreen(new EmoteMenu(this))) 39 | .build() 40 | ); 41 | } 42 | 43 | @Override 44 | protected void onPressed(EmoteListWidget.ListEntry selected) { 45 | if (selected instanceof EmoteListWidget.EmoteEntry entry && 46 | entry.getEmote().playEmote() && 47 | this.lastScreen instanceof FastMenuScreen fast 48 | ) { 49 | this.lastScreen = fast.parent; 50 | } 51 | } 52 | 53 | @Override 54 | protected void renderBlurredBackground(GuiGraphics guiGraphics) { 55 | if (this.minecraft.player instanceof IPlayerEntity entity && 56 | EmotePlayer.isRunningEmote(entity.emotecraft$getEmote()) 57 | ) { 58 | return; 59 | } 60 | 61 | super.renderBlurredBackground(guiGraphics); 62 | } 63 | 64 | @Override 65 | protected void repositionElements() { 66 | addOptions(); 67 | super.repositionElements(); 68 | this.layout.arrangeElements(); 69 | } 70 | 71 | @Override 72 | public void tick() { 73 | if (this.preview != null && this.list.getSelected() == this.list.getHovered()) { 74 | this.preview.getMannequin().stopEmote(); 75 | } 76 | super.tick(); 77 | } 78 | 79 | @Override 80 | public boolean isPauseScreen() { 81 | return false; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /minecraft/archCommon/src/main/java/io/github/kosmx/emotes/arch/screen/utils/EmoteListener.java: -------------------------------------------------------------------------------- 1 | package io.github.kosmx.emotes.arch.screen.utils; 2 | 3 | import com.google.common.base.Stopwatch; 4 | import io.github.kosmx.emotes.PlatformTools; 5 | import io.github.kosmx.emotes.arch.EmotecraftClientMod; 6 | import io.github.kosmx.emotes.common.CommonData; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.gui.screens.packs.PackSelectionScreen; 9 | import net.minecraft.network.chat.Component; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.*; 15 | import java.text.DecimalFormat; 16 | import java.util.Objects; 17 | import java.util.concurrent.CompletableFuture; 18 | import java.util.concurrent.Executor; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class EmoteListener extends PackSelectionScreen.Watcher { 22 | public static final Component RELOADING_WAIT = Component.translatable("emotecraft.reloading.wait"); 23 | public static final Component RELOADING = Component.translatable("emotecraft.reloading"); 24 | 25 | private static final DecimalFormat FORMAT = new DecimalFormat("#0.000"); 26 | 27 | private CompletableFuture loader; 28 | 29 | protected EmoteListener(Path path) throws IOException { 30 | super(path); 31 | } 32 | 33 | @Nullable 34 | public static EmoteListener create(Path packPath) { 35 | try { 36 | return new EmoteListener(packPath); 37 | } catch (IOException ex) { 38 | CommonData.LOGGER.warn("Failed to initialize emote dir monitoring", ex); 39 | return null; 40 | } 41 | } 42 | 43 | public void load(Runnable onComplete, @NotNull Executor executor) { 44 | if (this.loader != null) this.loader.cancel(true); 45 | PlatformTools.addToast(EmoteListener.RELOADING); 46 | 47 | Stopwatch stopwatch = Stopwatch.createStarted(); 48 | this.loader = EmotecraftClientMod.loadEmotes() 49 | .thenRun(() -> PlatformTools.addToast(Component.translatable("emotecraft.reloading.done", 50 | FORMAT.format((double) stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) / 1000D) 51 | ))) 52 | .thenRunAsync(onComplete, Objects.requireNonNullElseGet(executor, Minecraft::getInstance)); 53 | } 54 | 55 | public boolean isLoading() { 56 | return this.loader != null && !this.loader.isDone() && !this.loader.isCompletedExceptionally(); 57 | } 58 | 59 | @Override 60 | public void close() throws IOException { 61 | super.close(); 62 | 63 | if (this.loader != null) { 64 | this.loader.cancel(true); 65 | this.loader = null; 66 | } 67 | } 68 | 69 | public void blockWhileLoading() { 70 | if (this.loader != null && !this.loader.isDone() && !this.loader.isCompletedExceptionally()) { 71 | try { 72 | this.loader.get(10, TimeUnit.SECONDS); 73 | } catch (Throwable th) { 74 | CommonData.LOGGER.warn("Failed to wait for emote loading!", th); 75 | this.loader.cancel(true); 76 | } 77 | } 78 | } 79 | } 80 | --------------------------------------------------------------------------------