diff --git a/build.gradle b/build.gradle index 4e1042c..45ff93c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,21 @@ plugins { - id 'fabric-loom' version '1.4-SNAPSHOT' + id 'fabric-loom' version '1.5-SNAPSHOT' id 'maven-publish' + id 'idea' } -version = project.mod_version +version = project.mod_version + "+" + project.minecraft_version +supported_version = project.supported_version group = project.maven_group +processResources { + inputs.properties("supported_version": supported_version, "version": project.version) + + filesMatching("fabric.mod.json") { + expand "supported_version": supported_version, "version": project.version + } +} + repositories { // Add repositories to retrieve artifacts from in here. // You should only use this when depending on other mods because diff --git a/gradle.properties b/gradle.properties index af74d77..b490122 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.20.4 +supported_version=>=1.20.2 yarn_mappings=1.20.4+build.3 -loader_version=0.15.3 +loader_version=0.15.6 # Mod Properties -mod_version=4.0.3 +mod_version=5.0.0-beta.1 maven_group=dev.louis archives_base_name=Nebula # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.92.0+1.20.4 +fabric_version=0.95.0+1.20.4 diff --git a/nebulo/README.md b/nebulo/README.md new file mode 100644 index 0000000..023f128 --- /dev/null +++ b/nebulo/README.md @@ -0,0 +1,5 @@ +# Nebulo + +This is the test mod for Nebula. + +Feel free to use all code in this test mod. diff --git a/nebulo/build.gradle b/nebulo/build.gradle new file mode 100644 index 0000000..acdbfa3 --- /dev/null +++ b/nebulo/build.gradle @@ -0,0 +1,95 @@ +plugins { + id 'fabric-loom' version '1.5-SNAPSHOT' + id 'maven-publish' + id 'idea' +} + +version = project.mod_version + "+" + project.minecraft_version +supported_version = project.supported_version +group = project.maven_group + +processResources { + inputs.properties("supported_version": supported_version, "version": project.version) + + filesMatching("fabric.mod.json") { + expand "supported_version": supported_version, "version": project.version + } +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + implementation(project(path: ":", configuration: "namedElements")) { + exclude group: "net.fabricmc", module: "fabric-loader" + } +} + +processResources { + inputs.property "version", project.version + filteringCharset "UTF-8" + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +def targetJavaVersion = 17 +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + it.options.release = targetJavaVersion + } +} + +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } + archivesBaseName = project.archives_base_name + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/Nebulo.java b/nebulo/src/main/java/dev/louis/nebulo/Nebulo.java new file mode 100644 index 0000000..fc563e6 --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/Nebulo.java @@ -0,0 +1,16 @@ +package dev.louis.nebulo; + +import com.mojang.logging.LogUtils; +import net.fabricmc.api.ModInitializer; +import org.slf4j.Logger; + +public class Nebulo implements ModInitializer { + public static final String MOD_ID = "nebulo"; + public static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public void onInitialize() { + NebuloSpells.init(); + LOGGER.info("Nebulo has been initialized."); + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/NebuloSpells.java b/nebulo/src/main/java/dev/louis/nebulo/NebuloSpells.java new file mode 100644 index 0000000..60e779a --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/NebuloSpells.java @@ -0,0 +1,12 @@ +package dev.louis.nebulo; + +import dev.louis.nebula.api.spell.SpellType; +import dev.louis.nebulo.spell.CloudJumpSpell; +import net.minecraft.util.Identifier; + +public class NebuloSpells { + public static final SpellType CLOUD_JUMP = SpellType.register(new Identifier(Nebulo.MOD_ID, "cloud_jump"), SpellType.Builder.create(CloudJumpSpell::new, 2)); + public static void init() { + + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/client/NebuloClient.java b/nebulo/src/main/java/dev/louis/nebulo/client/NebuloClient.java new file mode 100644 index 0000000..9ea31e2 --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/client/NebuloClient.java @@ -0,0 +1,98 @@ +package dev.louis.nebulo.client; + +import dev.louis.nebula.api.spell.SpellType; +import dev.louis.nebulo.NebuloSpells; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; +import org.lwjgl.glfw.GLFW; + +import java.util.concurrent.atomic.AtomicInteger; + +public class NebuloClient implements ClientModInitializer { + public int spellCooldown = 0; + @Override + public void onInitializeClient() { + registerKeybind(); + registerKeybindCallback(); + + registerRenderCallback(); + } + + + private static void registerKeybind() { + var keyBind = new KeyBinding( + "key.nebulo.example", + GLFW.GLFW_KEY_UNKNOWN, + "key.categories.nebulo" + ); + KeyBindingHelper.registerKeyBinding(keyBind); + SpellKeybindManager.addSpellKeyBinding(NebuloSpells.CLOUD_JUMP, keyBind); + } + + private void registerKeybindCallback() { + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (spellCooldown > 0) { + spellCooldown--; + return; + } + + for (SpellType spellType : SpellType.REGISTRY) { + var optionalKey = SpellKeybindManager.getKey(spellType); + if(optionalKey.isPresent()) { + var key = optionalKey.get(); + if(key.isPressed()) { + client.player.getSpellManager().cast(spellType); + spellCooldown = 10; + return; + } + } + } + }); + } + + private void registerRenderCallback() { + HudRenderCallback.EVENT.register((drawContext, tickDelta) -> { + var player = MinecraftClient.getInstance().player; + if(player == null)return; + var manaManager = player.getManaManager(); + var spellManager = player.getSpellManager(); + var mana = String.valueOf(manaManager.getMana()); + var maxMana = String.valueOf(manaManager.getMaxMana()); + drawContext.drawText( + MinecraftClient.getInstance().textRenderer, + "Mana: " + mana + "/" + maxMana, + 10, + 10, + 0x0000FF, + false + ); + + drawContext.drawText( + MinecraftClient.getInstance().textRenderer, + "Learned Spells:", + 10, + 20, + 0x00FFFF, + true + ); + + var spells = spellManager.getLearnedSpells(); + AtomicInteger y = new AtomicInteger(30); + spells.forEach(spellType -> { + drawContext.drawText( + MinecraftClient.getInstance().textRenderer, + spellType.getId().toString(), + 10, + y.get(), + 0x03F6FF, + true + ); + y.addAndGet(10); + }); + }); + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/client/SpellKeybindManager.java b/nebulo/src/main/java/dev/louis/nebulo/client/SpellKeybindManager.java new file mode 100644 index 0000000..e530ef7 --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/client/SpellKeybindManager.java @@ -0,0 +1,48 @@ + +package dev.louis.nebulo.client; + + +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.option.KeyBinding; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Optional; + +/** + The SpellKeybinds class stores a mapping between spell types and key bindings. + It allows for easy retrieval of the key binding associated with a specific spell type. + */ +@Environment(EnvType.CLIENT) +public class SpellKeybindManager { + + // HashMap that stores the mapping between spell types and key bindings + private static final HashMap, KeyBinding> keyBindings = new HashMap<>(); + + /** + * Sets the key binding associated with a specific spell type. + * + * @param spellType the spell type to associate with the key binding + * @param keyBinding the key binding to associate with the spell type + */ + public static void addSpellKeyBinding(SpellType spellType, KeyBinding keyBinding) { + keyBindings.put(spellType, keyBinding); + } + + /** + * Returns an Optional containing the key binding associated with a specific spell type. + * If there is no key binding associated with the spell type, an empty Optional is returned. + * + * @param spellType the spell type to retrieve the key binding for + * @return an Optional containing the key binding associated with the spell type, or an empty Optional + */ + public static Optional getKey(@NotNull SpellType spellType){ + KeyBinding keyBinding = keyBindings.get(spellType); + if(keyBinding==null)return Optional.empty(); + return Optional.of(keyBinding); + } +} + diff --git a/nebulo/src/main/java/dev/louis/nebulo/manager/ManagerRegisterer.java b/nebulo/src/main/java/dev/louis/nebulo/manager/ManagerRegisterer.java new file mode 100644 index 0000000..900c56f --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/manager/ManagerRegisterer.java @@ -0,0 +1,27 @@ +package dev.louis.nebulo.manager; + +import dev.louis.nebula.api.manager.mana.entrypoint.RegisterManaManagerEntrypoint; +import dev.louis.nebula.api.manager.mana.registerable.ManaManagerRegistrableView; +import dev.louis.nebula.api.manager.spell.entrypoint.RegisterSpellManagerEntrypoint; +import dev.louis.nebula.api.manager.spell.registerable.SpellManagerRegistrableView; +import dev.louis.nebula.networking.SyncManaS2CPacket; +import dev.louis.nebula.networking.UpdateSpellCastabilityS2CPacket; +import dev.louis.nebulo.manager.mana.NebuloManaManager; +import dev.louis.nebulo.manager.spell.NebuloSpellManager; + +public class ManagerRegisterer implements RegisterManaManagerEntrypoint, RegisterSpellManagerEntrypoint { + @Override + public void registerSpell(ManaManagerRegistrableView manaManagerRegistrableView) { + manaManagerRegistrableView.registerManaManager(NebuloManaManager::new, SyncManaS2CPacket.ID, NebuloManaManager::receiveSync); + } + + @Override + public void registerSpell(SpellManagerRegistrableView spellManagerRegistrableView) { + spellManagerRegistrableView.registerSpellManager(NebuloSpellManager::new, UpdateSpellCastabilityS2CPacket.ID, NebuloSpellManager::receiveSync); + } + + @Override + public boolean shouldRegister() { + return true; + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/manager/mana/NebuloManaManager.java b/nebulo/src/main/java/dev/louis/nebulo/manager/mana/NebuloManaManager.java new file mode 100644 index 0000000..a3d2a05 --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/manager/mana/NebuloManaManager.java @@ -0,0 +1,10 @@ +package dev.louis.nebulo.manager.mana; + +import dev.louis.nebula.manager.mana.NebulaManaManager; +import net.minecraft.entity.player.PlayerEntity; + +public class NebuloManaManager extends NebulaManaManager { + public NebuloManaManager(PlayerEntity player) { + super(player); + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/manager/spell/NebuloSpellManager.java b/nebulo/src/main/java/dev/louis/nebulo/manager/spell/NebuloSpellManager.java new file mode 100644 index 0000000..a411643 --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/manager/spell/NebuloSpellManager.java @@ -0,0 +1,10 @@ +package dev.louis.nebulo.manager.spell; + +import dev.louis.nebula.manager.spell.NebulaSpellManager; +import net.minecraft.entity.player.PlayerEntity; + +public class NebuloSpellManager extends NebulaSpellManager { + public NebuloSpellManager(PlayerEntity player) { + super(player); + } +} diff --git a/nebulo/src/main/java/dev/louis/nebulo/spell/CloudJumpSpell.java b/nebulo/src/main/java/dev/louis/nebulo/spell/CloudJumpSpell.java new file mode 100644 index 0000000..6f961ed --- /dev/null +++ b/nebulo/src/main/java/dev/louis/nebulo/spell/CloudJumpSpell.java @@ -0,0 +1,71 @@ +package dev.louis.nebulo.spell; + +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; + +public class CloudJumpSpell extends Spell { + public CloudJumpSpell(SpellType spellType) { + super(spellType); + } + + @Override + public void cast() { + this.getCaster().playSound(SoundEvents.ENTITY_ARROW_HIT_PLAYER, SoundCategory.PLAYERS, 1f, 1f); + this.getCaster().addVelocity(0, 2, 0); + this.getCaster().velocityModified = true; + } + + @Override + public void tick() { + var world = this.getCaster().getWorld(); + if(!world.isClient()) { + var serverPlayer = (ServerPlayerEntity) this.getCaster(); + serverPlayer.getServerWorld().spawnParticles( + serverPlayer, + ParticleTypes.CLOUD, + false, + serverPlayer.getX(), + serverPlayer.getY(), + serverPlayer.getZ(), + 2, + 0, + 1, + 0, + 0.1 + ); + this.getCaster().playSound(SoundEvents.BLOCK_GLASS_HIT, SoundCategory.PLAYERS, 2f, -1f); + + } + } + + @Override + public int getDuration() { + return 10; + } + + @Override + public void onEnd() { + if(!this.getCaster().getWorld().isClient()) { + var serverPlayer = (ServerPlayerEntity) this.getCaster(); + serverPlayer.playSound(SoundEvents.ENTITY_CAMEL_DASH, SoundCategory.PLAYERS, 2f, -1f); + serverPlayer.getServerWorld().spawnParticles( + serverPlayer, + ParticleTypes.SMOKE, + false, + serverPlayer.getX(), + serverPlayer.getY(), + serverPlayer.getZ(), + 5, + 0, + 1, + 0, + 0.1 + ); + + } + } +} diff --git a/nebulo/src/main/resources/Nebulo.mixins.json b/nebulo/src/main/resources/Nebulo.mixins.json new file mode 100644 index 0000000..3bd4804 --- /dev/null +++ b/nebulo/src/main/resources/Nebulo.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.louis.nebulo.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/nebulo/src/main/resources/fabric.mod.json b/nebulo/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..c5d3eae --- /dev/null +++ b/nebulo/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "nebulo", + "version": "${version}", + "name": "Nebulo", + "description": "Nebulo is the Test Mod for Nebula.", + "authors": [ + "Louis" + ], + "contact": {}, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "dev.louis.nebulo.Nebulo" + ], + "client": [ + "dev.louis.nebulo.client.NebuloClient" + ], + "registerManaManager": [ + "dev.louis.nebulo.manager.ManagerRegisterer" + ], + "registerSpellManager": [ + "dev.louis.nebulo.manager.ManagerRegisterer" + ] + }, + "mixins": [ + "Nebulo.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.3", + "nebula": ">=5.0.0-", + "fabric": "*", + "minecraft": "${supported_version}" + } +} diff --git a/settings.gradle b/settings.gradle index f91a4fe..120b173 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,5 @@ pluginManagement { gradlePluginPortal() } } + +include(":nebulo") diff --git a/src/main/java/dev/louis/nebula/Nebula.java b/src/main/java/dev/louis/nebula/Nebula.java index 699101d..c5c2087 100644 --- a/src/main/java/dev/louis/nebula/Nebula.java +++ b/src/main/java/dev/louis/nebula/Nebula.java @@ -1,24 +1,18 @@ package dev.louis.nebula; import com.mojang.logging.LogUtils; +import dev.louis.nebula.api.spell.SpellType; import dev.louis.nebula.command.NebulaCommand; import dev.louis.nebula.networking.SpellCastC2SPacket; -import dev.louis.nebula.spell.SpellType; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; -import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.registry.Registry; -import net.minecraft.registry.RegistryKey; -import net.minecraft.registry.SimpleRegistry; -import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; +@ApiStatus.Internal public class Nebula implements ModInitializer { public static final String MOD_ID = "nebula"; public static final Logger LOGGER = LogUtils.getLogger(); - public static final RegistryKey>> SPELL_REGISTRY_KEY = RegistryKey.ofRegistry(new Identifier(MOD_ID, "spell_type")); - public static final SimpleRegistry> SPELL_REGISTRY = FabricRegistryBuilder.createSimple(SPELL_REGISTRY_KEY).attribute(RegistryAttribute.SYNCED).buildAndRegister(); @Override public void onInitialize() { @@ -30,7 +24,7 @@ public void onInitialize() { } public void registerPacketReceivers() { - ServerPlayNetworking.registerGlobalReceiver(SpellCastC2SPacket.getId(), SpellCastC2SPacket::receive); + ServerPlayNetworking.registerGlobalReceiver(SpellCastC2SPacket.TYPE, SpellCastC2SPacket::receive); } } diff --git a/src/main/java/dev/louis/nebula/NebulaClient.java b/src/main/java/dev/louis/nebula/NebulaClient.java index a0e0605..fe6e28d 100644 --- a/src/main/java/dev/louis/nebula/NebulaClient.java +++ b/src/main/java/dev/louis/nebula/NebulaClient.java @@ -1,37 +1,11 @@ package dev.louis.nebula; -import dev.louis.nebula.networking.SynchronizeManaAmountS2CPacket; -import dev.louis.nebula.networking.UpdateSpellCastabilityS2CPacket; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.minecraft.client.MinecraftClient; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.util.Identifier; public class NebulaClient implements ClientModInitializer { @Override public void onInitializeClient() { - registerPacketReceivers(); - } - - private void registerPacketReceivers() { - //Register the Spell Sync Packet. - registerReceiver(UpdateSpellCastabilityS2CPacket.getID(),UpdateSpellCastabilityS2CPacket::receive); - - //Register the ManaAmount Packet. - registerReceiver(SynchronizeManaAmountS2CPacket.getId(), SynchronizeManaAmountS2CPacket::receive); - } - - public static void runSyncWithBuf(MinecraftClient client, PacketByteBuf buf, Runnable runnable) { - buf.retain(); - client.executeSync(() -> { - runnable.run(); - buf.release(); - }); - } - - private void registerReceiver(Identifier id, ClientPlayNetworking.PlayChannelHandler playChannelHandler) { - ClientPlayNetworking.registerGlobalReceiver(id, playChannelHandler); + NebulaManager.registerPacketReceivers(); } } diff --git a/src/main/java/dev/louis/nebula/NebulaManager.java b/src/main/java/dev/louis/nebula/NebulaManager.java index f704179..25578d0 100644 --- a/src/main/java/dev/louis/nebula/NebulaManager.java +++ b/src/main/java/dev/louis/nebula/NebulaManager.java @@ -1,26 +1,38 @@ package dev.louis.nebula; -import dev.louis.nebula.api.manager.entrypoint.RegisterManaManagerEntrypoint; -import dev.louis.nebula.api.manager.entrypoint.RegisterSpellManagerEntrypoint; -import dev.louis.nebula.api.manager.registerable.ManaManagerRegistrableView; -import dev.louis.nebula.api.manager.registerable.SpellManagerRegistrableView; -import dev.louis.nebula.mana.manager.ManaManager; -import dev.louis.nebula.mana.manager.NebulaManaManager; -import dev.louis.nebula.spell.manager.NebulaSpellManager; -import dev.louis.nebula.spell.manager.SpellManager; +import dev.louis.nebula.api.manager.mana.ManaManager; +import dev.louis.nebula.api.manager.mana.entrypoint.RegisterManaManagerEntrypoint; +import dev.louis.nebula.api.manager.mana.registerable.ManaManagerRegistrableView; +import dev.louis.nebula.api.manager.spell.SpellManager; +import dev.louis.nebula.api.manager.spell.entrypoint.RegisterSpellManagerEntrypoint; +import dev.louis.nebula.api.manager.spell.registerable.SpellManagerRegistrableView; +import dev.louis.nebula.manager.mana.NebulaManaManager; +import dev.louis.nebula.manager.spell.NebulaSpellManager; +import dev.louis.nebula.networking.SyncManaS2CPacket; +import dev.louis.nebula.networking.UpdateSpellCastabilityS2CPacket; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.entrypoint.EntrypointContainer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; import java.util.List; +@ApiStatus.Internal public class NebulaManager implements ManaManagerRegistrableView, SpellManagerRegistrableView { private static ModContainer manaManagerMod; private static ManaManager.Factory manaManagerFactory; private static ModContainer spellManagerMod; private static SpellManager.Factory spellManagerFactory; private static boolean isLocked = false; + private static ClientPlayNetworking.PlayChannelHandler spellPlayChannelHandler; + private static ClientPlayNetworking.PlayChannelHandler manaPlayChannelHandler; + private static Identifier manaPacketId; + private static Identifier spellPacketId; private NebulaManager() {} @@ -50,24 +62,36 @@ public static SpellManager createSpellManager(PlayerEntity player) { public void lock() { if(spellManagerFactory == null) { - spellManagerFactory = NebulaSpellManager::new; + registerSpellManager(NebulaSpellManager::new, UpdateSpellCastabilityS2CPacket.ID, NebulaSpellManager::receiveSync); spellManagerMod = FabricLoader.getInstance().getModContainer(Nebula.MOD_ID).orElseThrow(); } if(manaManagerFactory == null) { - manaManagerFactory = NebulaManaManager::new; + registerManaManager(NebulaManaManager::new, SyncManaS2CPacket.ID, NebulaManaManager::receiveSync); manaManagerMod = FabricLoader.getInstance().getModContainer(Nebula.MOD_ID).orElseThrow(); } isLocked = true; } @Override - public void registerManaManagerFactory(ManaManager.Factory manaManagerFactory) { + public void registerManaManager( + ManaManager.Factory manaManagerFactory, + Identifier packetId, + ClientPlayNetworking.PlayChannelHandler manaChannelHandler + ) { NebulaManager.manaManagerFactory = manaManagerFactory; + NebulaManager.manaPacketId = packetId; + NebulaManager.manaPlayChannelHandler = manaChannelHandler; } @Override - public void registerSpellManagerFactory(SpellManager.Factory spellManagerFactory) { + public void registerSpellManager( + SpellManager.Factory spellManagerFactory, + Identifier packetId, + ClientPlayNetworking.PlayChannelHandler spellChannelHandler + ) { NebulaManager.spellManagerFactory = spellManagerFactory; + NebulaManager.spellPacketId = packetId; + NebulaManager.spellPlayChannelHandler = spellChannelHandler; } /** @@ -123,4 +147,15 @@ private void printInfo() { private static T getFirstOrNull(List list) { return list.isEmpty() ? null : list.get(0); } + + @Environment(EnvType.CLIENT) + public static void registerPacketReceivers() { + if(!NebulaManager.isLocked) { + throw new IllegalStateException("NebulaManager is not locked yet!"); + } + ClientPlayNetworking.registerGlobalReceiver(NebulaManager.manaPacketId, NebulaManager.manaPlayChannelHandler); + Nebula.LOGGER.info("Registered ManaManager Packet Receiver with id: " + NebulaManager.manaPacketId); + ClientPlayNetworking.registerGlobalReceiver(NebulaManager.spellPacketId, NebulaManager.spellPlayChannelHandler); + Nebula.LOGGER.info("Registered SpellManager Packet Receiver with id: " + NebulaManager.spellPacketId); + } } diff --git a/src/main/java/dev/louis/nebula/api/NebulaPlayer.java b/src/main/java/dev/louis/nebula/api/NebulaPlayer.java index a551b13..f5cac54 100644 --- a/src/main/java/dev/louis/nebula/api/NebulaPlayer.java +++ b/src/main/java/dev/louis/nebula/api/NebulaPlayer.java @@ -1,7 +1,7 @@ package dev.louis.nebula.api; -import dev.louis.nebula.mana.manager.ManaManager; -import dev.louis.nebula.spell.manager.SpellManager; +import dev.louis.nebula.api.manager.mana.ManaManager; +import dev.louis.nebula.api.manager.spell.SpellManager; import net.minecraft.entity.player.PlayerEntity; /** diff --git a/src/main/java/dev/louis/nebula/event/SpellCastCallback.java b/src/main/java/dev/louis/nebula/api/event/SpellCastCallback.java similarity index 83% rename from src/main/java/dev/louis/nebula/event/SpellCastCallback.java rename to src/main/java/dev/louis/nebula/api/event/SpellCastCallback.java index 052383d..0c93bba 100644 --- a/src/main/java/dev/louis/nebula/event/SpellCastCallback.java +++ b/src/main/java/dev/louis/nebula/api/event/SpellCastCallback.java @@ -1,11 +1,13 @@ -package dev.louis.nebula.event; +package dev.louis.nebula.api.event; -import dev.louis.nebula.spell.Spell; +import dev.louis.nebula.api.spell.Spell; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.ActionResult; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Experimental public interface SpellCastCallback { Event EVENT = EventFactory.createArrayBacked(SpellCastCallback.class, (listeners) -> (player, spell) -> { for (SpellCastCallback event : listeners) { diff --git a/src/main/java/dev/louis/nebula/mana/manager/ManaManager.java b/src/main/java/dev/louis/nebula/api/manager/mana/ManaManager.java similarity index 81% rename from src/main/java/dev/louis/nebula/mana/manager/ManaManager.java rename to src/main/java/dev/louis/nebula/api/manager/mana/ManaManager.java index 37b98c2..c2444e0 100644 --- a/src/main/java/dev/louis/nebula/mana/manager/ManaManager.java +++ b/src/main/java/dev/louis/nebula/api/manager/mana/ManaManager.java @@ -1,14 +1,10 @@ -package dev.louis.nebula.mana.manager; +package dev.louis.nebula.api.manager.mana; -import dev.louis.nebula.spell.SpellType; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; +import dev.louis.nebula.api.spell.SpellType; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; -import net.minecraft.network.PacketByteBuf; public interface ManaManager { /** @@ -61,7 +57,7 @@ public interface ManaManager { * @param spellType The SpellType which should be checked. * @return If enough mana is available for the specified SpellType. */ - boolean hasEnoughMana(SpellType spellType); + boolean isCastable(SpellType spellType); /** * Sends the ManaManager's state to the client. @@ -69,12 +65,6 @@ public interface ManaManager { */ boolean sendSync(); - /** - * Receives the ManaManager's state for the client. This shall never be called by the server. - * @return If the state was successfully received. - */ - boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); - /** * Writes the Nbt data of the SpellManager. * @param nbt The Nbt data that shall be written to. @@ -101,6 +91,10 @@ public interface ManaManager { */ ManaManager setPlayer(PlayerEntity player); + default boolean isEmpty() { + return false; + } + @FunctionalInterface interface Factory { T createPlayerManaManager(PlayerEntity player); @@ -154,7 +148,7 @@ public boolean hasEnoughMana(int mana) { } @Override - public boolean hasEnoughMana(SpellType spellType) { + public boolean isCastable(SpellType spellType) { return false; } @@ -163,11 +157,6 @@ public boolean sendSync() { return false; } - @Override - public boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - return false; - } - @Override public void writeNbt(NbtCompound nbt) { @@ -187,5 +176,10 @@ public void onDeath(DamageSource damageSource) { public ManaManager setPlayer(PlayerEntity player) { return this; } + + @Override + public boolean isEmpty() { + return true; + } }; } diff --git a/src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterManaManagerEntrypoint.java b/src/main/java/dev/louis/nebula/api/manager/mana/entrypoint/RegisterManaManagerEntrypoint.java similarity index 66% rename from src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterManaManagerEntrypoint.java rename to src/main/java/dev/louis/nebula/api/manager/mana/entrypoint/RegisterManaManagerEntrypoint.java index 58d9c5c..a890f0b 100644 --- a/src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterManaManagerEntrypoint.java +++ b/src/main/java/dev/louis/nebula/api/manager/mana/entrypoint/RegisterManaManagerEntrypoint.java @@ -1,6 +1,6 @@ -package dev.louis.nebula.api.manager.entrypoint; +package dev.louis.nebula.api.manager.mana.entrypoint; -import dev.louis.nebula.api.manager.registerable.ManaManagerRegistrableView; +import dev.louis.nebula.api.manager.mana.registerable.ManaManagerRegistrableView; /** * The entrypoint for registering a custom mana manager. diff --git a/src/main/java/dev/louis/nebula/api/manager/mana/registerable/ManaManagerRegistrableView.java b/src/main/java/dev/louis/nebula/api/manager/mana/registerable/ManaManagerRegistrableView.java new file mode 100644 index 0000000..c2e4e84 --- /dev/null +++ b/src/main/java/dev/louis/nebula/api/manager/mana/registerable/ManaManagerRegistrableView.java @@ -0,0 +1,13 @@ +package dev.louis.nebula.api.manager.mana.registerable; + +import dev.louis.nebula.api.manager.mana.ManaManager; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.util.Identifier; + +public interface ManaManagerRegistrableView { + void registerManaManager( + ManaManager.Factory manaManagerFactory, + Identifier packetId, + ClientPlayNetworking.PlayChannelHandler playChannelHandler + ); +} \ No newline at end of file diff --git a/src/main/java/dev/louis/nebula/api/manager/registerable/ManaManagerRegistrableView.java b/src/main/java/dev/louis/nebula/api/manager/registerable/ManaManagerRegistrableView.java deleted file mode 100644 index a510bd6..0000000 --- a/src/main/java/dev/louis/nebula/api/manager/registerable/ManaManagerRegistrableView.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.louis.nebula.api.manager.registerable; - -import dev.louis.nebula.mana.manager.ManaManager; - -public interface ManaManagerRegistrableView { - void registerManaManagerFactory(ManaManager.Factory manaManagerFactory); -} \ No newline at end of file diff --git a/src/main/java/dev/louis/nebula/api/manager/registerable/SpellManagerRegistrableView.java b/src/main/java/dev/louis/nebula/api/manager/registerable/SpellManagerRegistrableView.java deleted file mode 100644 index 2f618a7..0000000 --- a/src/main/java/dev/louis/nebula/api/manager/registerable/SpellManagerRegistrableView.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.louis.nebula.api.manager.registerable; - -import dev.louis.nebula.spell.manager.SpellManager; - -public interface SpellManagerRegistrableView { - void registerSpellManagerFactory(SpellManager.Factory manaManagerFactory); -} \ No newline at end of file diff --git a/src/main/java/dev/louis/nebula/spell/manager/SpellManager.java b/src/main/java/dev/louis/nebula/api/manager/spell/SpellManager.java similarity index 61% rename from src/main/java/dev/louis/nebula/spell/manager/SpellManager.java rename to src/main/java/dev/louis/nebula/api/manager/spell/SpellManager.java index 488ce50..06e5382 100644 --- a/src/main/java/dev/louis/nebula/spell/manager/SpellManager.java +++ b/src/main/java/dev/louis/nebula/api/manager/spell/SpellManager.java @@ -1,48 +1,20 @@ -package dev.louis.nebula.spell.manager; - -import dev.louis.nebula.spell.Spell; -import dev.louis.nebula.spell.SpellType; -import dev.louis.nebula.spell.TickingSpell; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; +package dev.louis.nebula.api.manager.spell; + +import dev.louis.nebula.api.event.SpellCastCallback; +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; -import net.minecraft.network.PacketByteBuf; + +import java.util.Collection; +import java.util.List; public interface SpellManager { void tick(); - /** - * This shouldn't be called directly, it is called by {@link TickingSpell#cast()} - * @param tickingSpell The TickingSpell which should start ticking. - * @return If the TickingSpell was successfully added. - */ - boolean startTickingSpell(TickingSpell tickingSpell); - - /** - * Stops the TickingSpell from ticking. - * {@link TickingSpell#stop()} will be called when this is called. - * @param tickingSpell The TickingSpell which should be stopped. - * @return If the TickingSpell was successfully stopped. - */ - boolean stopTickingSpell(TickingSpell tickingSpell); - - /** - * Checks if a TickingSpell of the specified SpellType is currently ticking. - * @param spellType The SpellType which should be checked. - * @return If a TickingSpell with the specified SpellType is currently ticking. - */ - boolean isSpellTypeTicking(SpellType spellType); - - /** - * Checks if a TickingSpell is currently ticking. - * @param tickingSpell The TickingSpell which should be checked. - * @return If the TickingSpell is currently ticking. - */ - boolean isSpellTicking(TickingSpell tickingSpell); + Collection> getLearnedSpells(); /** * Learns the specified SpellType. @@ -59,16 +31,16 @@ public interface SpellManager { boolean forgetSpell(SpellType spellType); /** - * Casts a Spell of the specified SpellType. The Spell may not cast if it is not castable, cancelled by {@link dev.louis.nebula.event.SpellCastCallback} or other reasons. + * Casts a Spell of the specified SpellType. The Spell may not cast if it is not castable, cancelled by {@link SpellCastCallback} or other reasons. * @param spellType The SpellType which should be cast. */ - void cast(SpellType spellType); + boolean cast(SpellType spellType); /** - * Casts a Spell. The Spell may not cast if it is not castable, cancelled by {@link dev.louis.nebula.event.SpellCastCallback} or other reasons. - * @param spell The Spell which should be casted. + * Casts a Spell. The Spell may not cast if it is not castable, cancelled by {@link SpellCastCallback} or other reasons. + * @param spell The Spell which should be cast. */ - void cast(Spell spell); + boolean cast(Spell spell); /** * This is called when the Caster dies. @@ -83,6 +55,25 @@ public interface SpellManager { */ boolean isCastable(SpellType spellType); + /** + * @return All active Spells. Do not modify. + */ + Collection getActiveSpells(); + + /** + * Checks if the specified SpellType is currently active. + * @param spellType The SpellType which should be checked. + * @return If the SpellType is currently active. + */ + boolean isSpellTypeActive(SpellType spellType); + + /** + * Checks if the specified Spell is currently active. + * @param spell The Spell which should be checked. + * @return If the Spell is currently active. + */ + boolean isSpellActive(Spell spell); + /** * Checks if the specified has been learned. * @param spellType The SpellType which should be checked. @@ -96,12 +87,6 @@ public interface SpellManager { */ boolean sendSync(); - /** - * Receives the SpellManager's state for the client. This shall never be called by the server. - * @return If the state was successfully received. - */ - boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); - /** * Writes the Nbt data of the SpellManager. * @param nbt The nbt data that shall be written to. @@ -122,6 +107,10 @@ public interface SpellManager { */ SpellManager setPlayer(PlayerEntity player); + default boolean isEmpty() { + return false; + } + @FunctionalInterface interface Factory { T createSpellKnowledgeManager(PlayerEntity player); @@ -139,67 +128,62 @@ public void tick() { } @Override - public boolean startTickingSpell(TickingSpell tickingSpell) { - return false; - } - - @Override - public boolean stopTickingSpell(TickingSpell tickingSpell) { - return false; + public Collection> getLearnedSpells() { + return List.of(); } @Override - public boolean isSpellTypeTicking(SpellType spellType) { + public boolean learnSpell(SpellType spellType) { return false; } @Override - public boolean isSpellTicking(TickingSpell tickingSpell) { + public boolean forgetSpell(SpellType spellType) { return false; } @Override - public boolean learnSpell(SpellType spellType) { + public boolean cast(SpellType spellType) { return false; } @Override - public boolean forgetSpell(SpellType spellType) { + public boolean cast(Spell spell) { return false; } @Override - public void cast(SpellType spellType) { + public void onDeath(DamageSource damageSource) { } @Override - public void cast(Spell spell) { - + public boolean isCastable(SpellType spellType) { + return false; } @Override - public void onDeath(DamageSource damageSource) { - + public Collection getActiveSpells() { + return List.of(); } @Override - public boolean isCastable(SpellType spellType) { + public boolean isSpellTypeActive(SpellType spellType) { return false; - } + }; @Override - public boolean hasLearned(SpellType spellType) { + public boolean isSpellActive(Spell spell) { return false; - } + }; @Override - public boolean sendSync() { + public boolean hasLearned(SpellType spellType) { return false; } @Override - public boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + public boolean sendSync() { return false; } @@ -217,5 +201,10 @@ public void readNbt(NbtCompound nbt) { public SpellManager setPlayer(PlayerEntity player) { return this; } + + @Override + public boolean isEmpty() { + return true; + } }; } diff --git a/src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterSpellManagerEntrypoint.java b/src/main/java/dev/louis/nebula/api/manager/spell/entrypoint/RegisterSpellManagerEntrypoint.java similarity index 65% rename from src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterSpellManagerEntrypoint.java rename to src/main/java/dev/louis/nebula/api/manager/spell/entrypoint/RegisterSpellManagerEntrypoint.java index a1ad19d..4c48820 100644 --- a/src/main/java/dev/louis/nebula/api/manager/entrypoint/RegisterSpellManagerEntrypoint.java +++ b/src/main/java/dev/louis/nebula/api/manager/spell/entrypoint/RegisterSpellManagerEntrypoint.java @@ -1,6 +1,6 @@ -package dev.louis.nebula.api.manager.entrypoint; +package dev.louis.nebula.api.manager.spell.entrypoint; -import dev.louis.nebula.api.manager.registerable.SpellManagerRegistrableView; +import dev.louis.nebula.api.manager.spell.registerable.SpellManagerRegistrableView; /** * The entrypoint for registering a custom spell manager. diff --git a/src/main/java/dev/louis/nebula/api/manager/spell/registerable/SpellManagerRegistrableView.java b/src/main/java/dev/louis/nebula/api/manager/spell/registerable/SpellManagerRegistrableView.java new file mode 100644 index 0000000..c8bffd0 --- /dev/null +++ b/src/main/java/dev/louis/nebula/api/manager/spell/registerable/SpellManagerRegistrableView.java @@ -0,0 +1,13 @@ +package dev.louis.nebula.api.manager.spell.registerable; + +import dev.louis.nebula.api.manager.spell.SpellManager; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.util.Identifier; + +public interface SpellManagerRegistrableView { + void registerSpellManager( + SpellManager.Factory manaManagerFactory, + Identifier packetId, + ClientPlayNetworking.PlayChannelHandler playChannelHandler + ); +} \ No newline at end of file diff --git a/src/main/java/dev/louis/nebula/api/spell/Spell.java b/src/main/java/dev/louis/nebula/api/spell/Spell.java new file mode 100644 index 0000000..9cd076a --- /dev/null +++ b/src/main/java/dev/louis/nebula/api/spell/Spell.java @@ -0,0 +1,179 @@ +package dev.louis.nebula.api.spell; + +import dev.louis.nebula.api.manager.spell.SpellManager; +import net.minecraft.entity.data.TrackedDataHandler; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; + +import java.util.Optional; + +/** + * This class represents an attempt to cast a spell. It holds a reference to the caster of the Spell. + * + */ +public abstract class Spell { + public static final TrackedDataHandler> OPTIONAL_SPELL = new TrackedDataHandler.ImmutableHandler<>() { + public void write(PacketByteBuf buf, Optional optionalSpell) { + buf.writeBoolean(optionalSpell.isPresent()); + optionalSpell.ifPresent(spell -> { + buf.writeRegistryValue(SpellType.REGISTRY, spell.getType()); + spell.writeBuf(buf); + }); + } + + public Optional read(PacketByteBuf buf) { + if(!buf.readBoolean()) return Optional.empty(); + SpellType spellType = buf.readRegistryValue(SpellType.REGISTRY); + if(spellType == null) throw new IllegalStateException("Spell type not found in registry"); + var spell = spellType.create(); + spell.readBuf(buf); + return Optional.of(spell); + } + }; + private static final int DEFAULT_SPELL_AGE = 3 * 20; + + private final SpellType spellType; + private PlayerEntity caster; + + protected int spellAge = 0; + protected boolean wasInterrupted; + protected boolean hasEnded; + protected boolean stopped; + + public Spell(SpellType spellType) { + this.spellType = spellType; + } + + /** + * Is called after {@link Spell#isCastable()} if the return of the method is true. + * This should not be called manually. + * Use {@link SpellManager#cast(Spell)} or {@link SpellManager#cast(SpellType)} + */ + public abstract void cast(); + + /** + * Remove the Cost required by the SpellType. + */ + public void applyCost() { + this.getCaster().getManaManager().drainMana(getType().getManaCost()); + } + + public final void baseTick() { + spellAge++; + this.tick(); + } + + public void tick() { + } + + public Identifier getId() { + return this.getType().getId(); + } + + public PlayerEntity getCaster() { + return this.caster; + } + + public SpellType getType() { + return this.spellType; + } + + public int getAge() { + return this.spellAge; + } + + public int getDuration() { + return DEFAULT_SPELL_AGE; + } + + /** + * This method is called if the spell ends.
+ * After this method is called {@link Spell#tick()} will not be called anymore.
+ * Use this to finish all remaining logic of the spell. + */ + public void onEnd() { + this.hasEnded = true; + } + + public void setCaster(PlayerEntity caster) { + this.caster = caster; + } + + /** + * Used to check if the spell should be stopped.
+ * It is important to check super or check if the spell was interrupted {@link Spell#wasInterrupted()}.
+ */ + public boolean shouldStop() { + return this.stopped || this.spellAge > this.getDuration(); + } + + /** + * Interrupts the spell.
+ * This method is final as no Spell is immune to being interrupted
+ *
+ * If you want to show to the player that the spell was interrupted check {@link Spell#wasInterrupted()} in {@link Spell#onEnd()} as it is called after this. + */ + public final void interrupt() { + this.wasInterrupted = true; + this.stop(); + } + + /** + * Stop the spell.
+ * This method is final as post-spell action shall be handled in {@link Spell#onEnd()}
+ *
+ * Unlike {@link Spell#interrupt()} if you call this method it is expected that the spell has finished execution.
+ */ + public final void stop() { + this.stopped = true; + } + + /** + * If true {@link Spell#applyCost()} and {@link Spell#cast()} will be called in that order.
+ * If false nothing will be called. + */ + public final boolean isCastable() { + return this.getType().isCastable(this.caster); + } + + public boolean isClient() { + return this.getCaster().getWorld().isClient(); + } + + public boolean wasInterrupted() { + return this.wasInterrupted; + } + + public boolean hasEnded() { + return this.hasEnded; + } + + /** + * Read additional casting data about the spell from the buf. + * @param buf The buf to be read from. + * @return The buf after being read from. + */ + public PacketByteBuf readBuf(PacketByteBuf buf) { + return buf; + } + + /** + * Write additional casting data about the spell to the buf. + * @param buf The buf to be written to. + * @return The buf after being written to. + */ + public PacketByteBuf writeBuf(PacketByteBuf buf) { + return buf; + } + + + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[spellType=" + this.spellType + + ", caster=" + this.caster + + ", spellAge=" + this.spellAge + + ", wasInterrupted=" + this.wasInterrupted + "]"; + } +} diff --git a/src/main/java/dev/louis/nebula/api/spell/SpellType.java b/src/main/java/dev/louis/nebula/api/spell/SpellType.java new file mode 100644 index 0000000..9706f90 --- /dev/null +++ b/src/main/java/dev/louis/nebula/api/spell/SpellType.java @@ -0,0 +1,141 @@ +package dev.louis.nebula.api.spell; + +import dev.louis.nebula.Nebula; +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.fabricmc.fabric.api.event.registry.RegistryAttribute; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.SimpleRegistry; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; + +public class SpellType { + private static final RegistryKey>> REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier(Nebula.MOD_ID, "spell_type")); + public static final SimpleRegistry> REGISTRY = + FabricRegistryBuilder.createSimple(REGISTRY_KEY).attribute(RegistryAttribute.SYNCED).buildAndRegister(); + + private final SpellFactory factory; + private final int manaCost; + private final boolean allowsMultipleCasts; + private final boolean needLearning; + private final SpellCastingValidator castabilityFunction; + + @ApiStatus.Internal + public SpellType(SpellFactory factory, int manaCost, boolean allowsMultipleCasts, boolean needLearning, SpellCastingValidator castabilityFunction) { + this.factory = factory; + this.manaCost = manaCost; + this.allowsMultipleCasts = allowsMultipleCasts; + this.needLearning = needLearning; + this.castabilityFunction = castabilityFunction; + } + + public static void init() { + } + + public static SpellType register(Identifier id, Builder type) { + return Registry.register(REGISTRY, id, type.build()); + } + + public static Optional> get(Identifier id) { + return REGISTRY.getOrEmpty(id); + } + + public Identifier getId() { + return REGISTRY.getId(this); + } + + public boolean isCastable(PlayerEntity player) { + return castabilityFunction.isCastable(this, player); + } + + public boolean allowsMultipleCasts() { + return allowsMultipleCasts; + } + + public boolean needsLearning() { + return needLearning; + } + + public boolean hasLearned(PlayerEntity player) { + return player.getSpellManager().hasLearned(this); + } + + public int getManaCost() { + return manaCost; + } + + public T create() { + return this.factory.create(this); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{id=" + this.getId() + ", manaCost=" + this.getManaCost() + "}"; + } + + public static class Builder { + private final SpellFactory factory; + private final int manaCost; + private boolean allowsMultipleCasts; + private boolean needsLearning = true; + private SpellCastingValidator castabilityFunction = SpellCastingValidator.DEFAULT; + + private Builder(SpellFactory factory, int manaCost) { + this.factory = factory; + this.manaCost = manaCost; + } + + public static Builder create(SpellFactory factory, int manaCost) { + return new Builder<>(factory, manaCost); + } + + /** + * Allows players to cast a spell multiple times at the same time.
+ * That means that a spell could be cast while it is already ticking + */ + public Builder allowMultipleCasts() { + this.allowsMultipleCasts = true; + return this; + } + + public Builder needsLearning(boolean needsLearning) { + this.needsLearning = needsLearning; + return this; + } + + public Builder castabilityFunction(SpellCastingValidator castabilityFunction) { + this.castabilityFunction = castabilityFunction; + return this; + } + + public SpellType build() { + return new SpellType<>(this.factory, manaCost, allowsMultipleCasts, needsLearning, castabilityFunction); + } + } + + @FunctionalInterface + public interface SpellFactory { + T create(SpellType spellType); + } + + @FunctionalInterface + public interface SpellCastingValidator { + SpellCastingValidator ALWAYS_CASTABLE = (spellType, player) -> true; + SpellCastingValidator DEFAULT = (spellType, player) -> player.getManaManager().isCastable(spellType) && player.getSpellManager().isCastable(spellType); + SpellCastingValidator NEVER_CASTABLE = (spellType, player) -> false; + + boolean isCastable(SpellType spellType, PlayerEntity player); + + default SpellCastingValidator and(SpellCastingValidator other) { + return (spellType, player) -> this.isCastable(spellType, player) && other.isCastable(spellType, player); + } + + default SpellCastingValidator or(SpellCastingValidator other) { + return (spellType, player) -> this.isCastable(spellType, player) || other.isCastable(spellType, player); + } + } +} diff --git a/src/main/java/dev/louis/nebula/api/spell/entity/SpellEntity.java b/src/main/java/dev/louis/nebula/api/spell/entity/SpellEntity.java new file mode 100644 index 0000000..cd7163b --- /dev/null +++ b/src/main/java/dev/louis/nebula/api/spell/entity/SpellEntity.java @@ -0,0 +1,95 @@ +package dev.louis.nebula.api.spell.entity; + +import dev.louis.nebula.api.spell.Spell; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.world.World; + +import java.util.Optional; + +/** + * An Entity that holds a Spell. Saving is disabled by default. + * This class can be extended to implement complex Logic for spells. + *

+ * The Entity will not be removed when the spell is stopped.
+ * This needs to be done by the spell a good place to do this is {@link Spell#onEnd()} + *

+ * @param The Spell that is being represented. + */ +public abstract class SpellEntity extends Entity { + protected static final TrackedData> SPELL = DataTracker.registerData(SpellEntity.class, Spell.OPTIONAL_SPELL); + + public SpellEntity(EntityType type, World world) { + super(type, world); + } + + @Override + protected void initDataTracker() { + this.dataTracker.startTracking(SPELL, Optional.empty()); + } + + @Override + public void baseTick() { + if(shouldDiscardWithSpell() && (this.getSpell().isEmpty() || this.getSpell().get().hasEnded())) { + this.remove(RemovalReason.DISCARDED); + return; + } + super.baseTick(); + } + + /** + * Replaces the current spell with the given spell. + * This syncs the spell to the client. Regardless of the current state of the TrackedData. + * @param spell The spell to set. If the spell is null, the spell will be empty. + */ + public void setSpell(T spell) { + //We always force set as the Spell is mutable and setting is the only way to force a sync. + this.dataTracker.set(SPELL, Optional.ofNullable(spell), true); + } + + /** + * Syncs the spell to the client. + * This needs to be done manually because the spell is mutable. + */ + public void syncSpell() { + this.dataTracker.set(SPELL, this.dataTracker.get(SPELL), true); + } + + public Optional getSpell() { + //noinspection unchecked + return (Optional) this.dataTracker.get(SPELL); + } + + public boolean shouldDiscardWithSpell() { + return true; + } + + /** + * SpellEntity does not save to disk by default. + * Still call super as future Versions might implement logic here. + */ + @Override + protected void readCustomDataFromNbt(NbtCompound nbt) { + + } + + /** + * SpellEntity does not save to disk by default. + * Still call super as future Versions might implement logic here. + */ + @Override + protected void writeCustomDataToNbt(NbtCompound nbt) { + + } + + /** + * SpellEntity does not save to disk by default. + */ + @Override + public boolean shouldSave() { + return false; + } +} diff --git a/src/main/java/dev/louis/nebula/command/NebulaCommand.java b/src/main/java/dev/louis/nebula/command/NebulaCommand.java index 56a0afa..8089bcf 100644 --- a/src/main/java/dev/louis/nebula/command/NebulaCommand.java +++ b/src/main/java/dev/louis/nebula/command/NebulaCommand.java @@ -1,7 +1,7 @@ package dev.louis.nebula.command; import com.mojang.brigadier.CommandDispatcher; -import dev.louis.nebula.Nebula; +import dev.louis.nebula.api.spell.SpellType; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.server.command.CommandManager; @@ -36,7 +36,7 @@ private static void register(CommandDispatcher dispatcher, command.then(setManaCommand); var learnSpellCommand = literal("learnSpell"); - Nebula.SPELL_REGISTRY.forEach(spellType -> { + SpellType.REGISTRY.forEach(spellType -> { learnSpellCommand.then(CommandManager.literal(spellType.getId().toString()).executes(context -> { if(context.getSource().isExecutedByPlayer()) { context.getSource().getPlayer().getSpellManager().learnSpell(spellType); diff --git a/src/main/java/dev/louis/nebula/mana/manager/NebulaManaManager.java b/src/main/java/dev/louis/nebula/manager/mana/NebulaManaManager.java similarity index 75% rename from src/main/java/dev/louis/nebula/mana/manager/NebulaManaManager.java rename to src/main/java/dev/louis/nebula/manager/mana/NebulaManaManager.java index 7166c8b..be75e47 100644 --- a/src/main/java/dev/louis/nebula/mana/manager/NebulaManaManager.java +++ b/src/main/java/dev/louis/nebula/manager/mana/NebulaManaManager.java @@ -1,8 +1,9 @@ -package dev.louis.nebula.mana.manager; +package dev.louis.nebula.manager.mana; import dev.louis.nebula.Nebula; -import dev.louis.nebula.networking.SynchronizeManaAmountS2CPacket; -import dev.louis.nebula.spell.SpellType; +import dev.louis.nebula.api.manager.mana.ManaManager; +import dev.louis.nebula.api.spell.SpellType; +import dev.louis.nebula.networking.SyncManaS2CPacket; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.client.MinecraftClient; @@ -13,11 +14,15 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.server.network.ServerPlayerEntity; +/** + * Extending this class is okay, but be aware this implementation! + */ public class NebulaManaManager implements ManaManager { protected static final String MANA_NBT_KEY = "Mana"; protected PlayerEntity player; protected int mana = 0; protected int lastSyncedMana = -1; + private boolean dirty; public NebulaManaManager(PlayerEntity player) { this.player = player; @@ -25,6 +30,10 @@ public NebulaManaManager(PlayerEntity player) { @Override public void tick() { + if(this.dirty) { + this.sendSync(); + this.dirty = false; + } } @Override @@ -39,7 +48,7 @@ public void setMana(int mana) { public void setMana(int mana, boolean syncToClient) { this.mana = Math.max(Math.min(mana, this.getMaxMana()), 0); - if(syncToClient) this.sendSync(); + if(syncToClient) this.markDirty(); } @Override @@ -68,33 +77,29 @@ public boolean hasEnoughMana(int mana) { } @Override - public boolean hasEnoughMana(SpellType spellType) { + public boolean isCastable(SpellType spellType) { return this.hasEnoughMana(spellType.getManaCost()); } + public void markDirty() { + this.dirty = true; + } + @Override public boolean sendSync() { if (this.player instanceof ServerPlayerEntity serverPlayerEntity && serverPlayerEntity.networkHandler != null) { int syncMana = this.getMana(); if (syncMana == this.lastSyncedMana) return true; this.lastSyncedMana = syncMana; - ServerPlayNetworking.send( - serverPlayerEntity, - new SynchronizeManaAmountS2CPacket(syncMana) - ); + ServerPlayNetworking.send(serverPlayerEntity, new SyncManaS2CPacket(syncMana)); return true; } return false; } - @Override - public boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - if(this.isServer()) { - Nebula.LOGGER.error("Called receiveSync on server side!"); - return false; - } - var packet = new SynchronizeManaAmountS2CPacket(buf); - this.player.getManaManager().setMana(packet.mana()); + public static boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + var packet = SyncManaS2CPacket.read(buf); + client.executeSync(() -> client.player.getManaManager().setMana(packet.mana())); return true; } diff --git a/src/main/java/dev/louis/nebula/manager/spell/NebulaSpellManager.java b/src/main/java/dev/louis/nebula/manager/spell/NebulaSpellManager.java new file mode 100644 index 0000000..2b21cb8 --- /dev/null +++ b/src/main/java/dev/louis/nebula/manager/spell/NebulaSpellManager.java @@ -0,0 +1,219 @@ +package dev.louis.nebula.manager.spell; + +import dev.louis.nebula.Nebula; +import dev.louis.nebula.api.event.SpellCastCallback; +import dev.louis.nebula.api.manager.spell.SpellManager; +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; +import dev.louis.nebula.networking.SpellCastC2SPacket; +import dev.louis.nebula.networking.UpdateSpellCastabilityS2CPacket; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Identifier; + +import java.util.*; + +/** + * Extending this class is okay, but be aware this implementation! + */ +public class NebulaSpellManager implements SpellManager { + protected static final String SPELL_NBT_KEY = "Spell"; + protected static final String SPELLS_NBT_KEY = "Spells"; + + protected final Set> learnedSpells = new HashSet<>(); + //Note: Not yet synced to client. + protected final Set activeSpells = new HashSet<>(); + protected PlayerEntity player; + private boolean dirty; + + public NebulaSpellManager(PlayerEntity player) { + this.player = player; + } + + @Override + public void tick() { + this.activeSpells.removeIf(spell -> { + boolean shouldStop = spell.shouldStop(); + if(shouldStop) spell.onEnd(); + return shouldStop; + }); + + for (Spell tickingSpell : this.activeSpells) { + tickingSpell.baseTick(); + } + if(dirty) this.sendSync(); + } + + @Override + public Collection> getLearnedSpells() { + return List.copyOf(this.learnedSpells); + } + + @Override + public boolean learnSpell(SpellType spellType) { + boolean shouldSync = this.learnedSpells.add(spellType); + if(shouldSync) this.markDirty(); + return true; + } + + @Override + public boolean forgetSpell(SpellType spellType) { + boolean shouldSync = this.learnedSpells.remove(spellType); + if(shouldSync) this.markDirty(); + return true; + } + + /** + * This Method does not return an unmodifiable set. + * So that it could theoretically be modified. + */ + protected Set> getCastableSpells() { + return learnedSpells; + } + + @Override + public boolean cast(SpellType spellType) { + var spell = spellType.create(); + spell.setCaster(this.player); + return this.cast(spell); + } + + @Override + public boolean cast(Spell spell) { + this.ensurePlayerEqualsCaster(spell); + this.ensureSpellIsNotAlreadyActive(spell); + if(SpellCastCallback.EVENT.invoker().interact(this.player, spell) != ActionResult.PASS) return false; + if(spell.isCastable()) { + if(this.isServer()) { + spell.applyCost(); + spell.cast(); + this.activeSpells.add(spell); + } else { + ClientPlayNetworking.send(new SpellCastC2SPacket(spell)); + } + return true; + } + return false; + } + + @Override + public void onDeath(DamageSource damageSource) { + //We don't clear the spells here because that could cause and ConcurrentModificationException if the player gets killed while the spells ends. + //Spell#interrupt is designed to cancel the spell no matter what, so it's fine cause the active spells are cleared next tick. + this.activeSpells.forEach(Spell::interrupt); + this.learnedSpells.clear(); + this.markDirty(); + } + + @Override + public boolean isCastable(SpellType spellType) { + return this.player.isAlive() && (!spellType.needsLearning() || this.hasLearned(spellType)) && (spellType.allowsMultipleCasts() || !player.getSpellManager().isSpellTypeActive(spellType)); + } + + public void markDirty() { + this.dirty = true; + } + + @Override + public Collection getActiveSpells() { + return List.copyOf(activeSpells); + } + + @Override + public boolean isSpellTypeActive(SpellType spellType) { + return this.activeSpells.stream().anyMatch(spell -> spell.getType().equals(spellType)); + }; + + @Override + public boolean isSpellActive(Spell spell) { + return this.activeSpells.contains(spell); + }; + + @Override + public boolean hasLearned(SpellType spellType) { + return this.learnedSpells.contains(spellType); + } + + @Override + public boolean sendSync() { + if(this.player instanceof ServerPlayerEntity serverPlayerEntity && serverPlayerEntity.networkHandler != null) { + Map, Boolean> castableSpells = new HashMap<>(); + SpellType.REGISTRY.forEach(spellType -> castableSpells.put(spellType, this.hasLearned(spellType))); + ServerPlayNetworking.send(serverPlayerEntity, new UpdateSpellCastabilityS2CPacket(castableSpells)); + dirty = false; + return true; + } + return false; + } + + + public static boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + UpdateSpellCastabilityS2CPacket packet = UpdateSpellCastabilityS2CPacket.read(buf); + MinecraftClient.getInstance().executeSync(() -> { + var spellManager = client.player.getSpellManager(); + packet.spells().forEach((spellType, learned) -> { + if (learned) spellManager.learnSpell(spellType); + else spellManager.forgetSpell(spellType); + }); + }); + return true; + } + + @Override + public void writeNbt(NbtCompound nbt) { + NbtList nbtList = new NbtList(); + for (SpellType spell : getCastableSpells()) { + NbtCompound nbtCompound = new NbtCompound(); + nbtCompound.putString(SPELL_NBT_KEY, spell.getId().toString()); + + nbtList.add(nbtCompound); + } + NbtCompound nebulaNbt = nbt.getCompound(Nebula.MOD_ID); + nebulaNbt.put(SPELLS_NBT_KEY, nbtList); + nbt.put(Nebula.MOD_ID, nebulaNbt); + } + + @Override + public void readNbt(NbtCompound nbt) { + NbtList nbtList = (NbtList) nbt.getCompound(Nebula.MOD_ID).get(SPELLS_NBT_KEY); + if(nbtList == null)return; + for (int x = 0; x < nbtList.size(); ++x) { + NbtCompound nbtCompound = nbtList.getCompound(x); + Identifier spell = new Identifier(nbtCompound.getString(SPELL_NBT_KEY)); + SpellType.get(spell).ifPresent(learnedSpells::add); + } + } + + @Override + public SpellManager setPlayer(PlayerEntity player) { + this.player = player; + return this; + } + + + private void ensureSpellIsNotAlreadyActive(Spell spell) { + if(this.activeSpells.contains(spell)) { + throw new IllegalStateException("Spell " + spell.getType() + " is already ticking!"); + } + } + + protected void ensurePlayerEqualsCaster(Spell spell) { + if(spell.getCaster() != this.player) { + throw new IllegalStateException("Spell " + spell.getType() + " was casted by " + spell.getCaster() + " but expected " + this.player); + } + } + + public boolean isServer() { + return !player.getWorld().isClient(); + } +} diff --git a/src/main/java/dev/louis/nebula/mixin/ClientPlayerEntityAccessor.java b/src/main/java/dev/louis/nebula/mixin/ClientPlayerEntityAccessor.java new file mode 100644 index 0000000..de4833d --- /dev/null +++ b/src/main/java/dev/louis/nebula/mixin/ClientPlayerEntityAccessor.java @@ -0,0 +1,12 @@ +package dev.louis.nebula.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientPlayerEntity.class) +public interface ClientPlayerEntityAccessor { + @Accessor + MinecraftClient getClient(); +} diff --git a/src/main/java/dev/louis/nebula/mixin/PlayerMixin.java b/src/main/java/dev/louis/nebula/mixin/PlayerMixin.java index ecdea36..5458c0e 100644 --- a/src/main/java/dev/louis/nebula/mixin/PlayerMixin.java +++ b/src/main/java/dev/louis/nebula/mixin/PlayerMixin.java @@ -2,8 +2,8 @@ import dev.louis.nebula.NebulaManager; import dev.louis.nebula.api.NebulaPlayer; -import dev.louis.nebula.mana.manager.ManaManager; -import dev.louis.nebula.spell.manager.SpellManager; +import dev.louis.nebula.api.manager.mana.ManaManager; +import dev.louis.nebula.api.manager.spell.SpellManager; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; @@ -74,7 +74,7 @@ public SpellManager setSpellManager(SpellManager spellManager) { @Override public void createManagersIfNecessary() { - if (this.manaManager == ManaManager.EMPTY) this.setManaManager(NebulaManager.createManaManager((PlayerEntity) (Object) this)); - if (this.spellManager == SpellManager.EMPTY) this.setSpellManager(NebulaManager.createSpellManager((PlayerEntity) (Object) this)); + if (this.manaManager.isEmpty()) this.setManaManager(NebulaManager.createManaManager((PlayerEntity) (Object) this)); + if (this.spellManager.isEmpty()) this.setSpellManager(NebulaManager.createSpellManager((PlayerEntity) (Object) this)); } } diff --git a/src/main/java/dev/louis/nebula/mixin/ServerPlayerEntityMixin.java b/src/main/java/dev/louis/nebula/mixin/ServerPlayerEntityMixin.java index 1a283b7..0a0023a 100644 --- a/src/main/java/dev/louis/nebula/mixin/ServerPlayerEntityMixin.java +++ b/src/main/java/dev/louis/nebula/mixin/ServerPlayerEntityMixin.java @@ -35,6 +35,9 @@ public void syncManaAndSpellsOnSpawn(CallbackInfo ci) { if(!hasManaSynced) Nebula.LOGGER.info("Mana could not be synced!"); } + /** + * Needed as {@link ServerPlayerEntity#onDeath} does not call {@link PlayerEntity#onDeath} + **/ @Inject( method = "onDeath", at = @At("RETURN") diff --git a/src/main/java/dev/louis/nebula/networking/SpellCastC2SPacket.java b/src/main/java/dev/louis/nebula/networking/SpellCastC2SPacket.java index c84a93d..8cd053a 100644 --- a/src/main/java/dev/louis/nebula/networking/SpellCastC2SPacket.java +++ b/src/main/java/dev/louis/nebula/networking/SpellCastC2SPacket.java @@ -1,42 +1,45 @@ package dev.louis.nebula.networking; import dev.louis.nebula.Nebula; -import dev.louis.nebula.spell.Spell; +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketType; import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; -import java.util.Objects; - +/** + * Not a FabricPacket because of Restriction in the api. + */ public record SpellCastC2SPacket(Spell spell) implements FabricPacket { - public static final PacketType PACKET_TYPE = PacketType.create(new Identifier(Nebula.MOD_ID, "spellcast"), SynchronizeManaAmountS2CPacket::new); + public static final Identifier ID = new Identifier(Nebula.MOD_ID, "spellcast"); + public static final PacketType TYPE = PacketType.create(ID, SpellCastC2SPacket::read); + + public static SpellCastC2SPacket read(PacketByteBuf buf) { + SpellType spellType = buf.readRegistryValue(SpellType.REGISTRY); + if(spellType == null) throw new IllegalStateException("Spell type not found in registry"); + Spell spell = spellType.create(); + spell.readBuf(buf); + return new SpellCastC2SPacket(spell); + } + public void write(PacketByteBuf buf) { - buf.writeRegistryValue(Nebula.SPELL_REGISTRY, spell.getType()); + buf.writeRegistryValue(SpellType.REGISTRY, spell.getType()); spell.writeBuf(buf); } @Override public PacketType getType() { - return PACKET_TYPE; + return TYPE; } - public static SpellCastC2SPacket read(ServerPlayerEntity caster, PacketByteBuf buf) { - Spell spell = buf.readRegistryValue(Nebula.SPELL_REGISTRY).create(caster); - spell.readBuf(buf); - return new SpellCastC2SPacket(spell); - } - public static void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - SpellCastC2SPacket packet = SpellCastC2SPacket.read(player, buf); - Objects.requireNonNull(packet); - Spell spell = packet.spell(); - player.getSpellManager().cast(spell); - } - public static Identifier getId() { - return PACKET_TYPE.getId(); + public static void receive(SpellCastC2SPacket packet, ServerPlayerEntity player, PacketSender responseSender) { + var spell = packet.spell(); + spell.setCaster(player); + player.server.executeSync(() -> { + player.getSpellManager().cast(spell); + }); } } diff --git a/src/main/java/dev/louis/nebula/networking/SyncManaS2CPacket.java b/src/main/java/dev/louis/nebula/networking/SyncManaS2CPacket.java new file mode 100644 index 0000000..b8009fb --- /dev/null +++ b/src/main/java/dev/louis/nebula/networking/SyncManaS2CPacket.java @@ -0,0 +1,25 @@ +package dev.louis.nebula.networking; + +import dev.louis.nebula.Nebula; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketType; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; + +public record SyncManaS2CPacket(int mana) implements FabricPacket { + public static final Identifier ID = new Identifier(Nebula.MOD_ID, "synchronizemana"); + public static final PacketType TYPE = PacketType.create(ID, SyncManaS2CPacket::read); + + public static SyncManaS2CPacket read(PacketByteBuf buf) { + return new SyncManaS2CPacket(buf.readVarInt()); + } + + public void write(PacketByteBuf buf) { + buf.writeVarInt(mana); + } + + @Override + public PacketType getType() { + return TYPE; + } +} diff --git a/src/main/java/dev/louis/nebula/networking/SynchronizeManaAmountS2CPacket.java b/src/main/java/dev/louis/nebula/networking/SynchronizeManaAmountS2CPacket.java deleted file mode 100644 index 06ede9c..0000000 --- a/src/main/java/dev/louis/nebula/networking/SynchronizeManaAmountS2CPacket.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.louis.nebula.networking; - -import dev.louis.nebula.Nebula; -import net.fabricmc.fabric.api.networking.v1.FabricPacket; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.fabricmc.fabric.api.networking.v1.PacketType; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.util.Identifier; - -import static dev.louis.nebula.NebulaClient.runSyncWithBuf; - -public record SynchronizeManaAmountS2CPacket(int mana) implements FabricPacket { - public static final PacketType PACKET_TYPE = PacketType.create(new Identifier(Nebula.MOD_ID, "synchronizemana"), SynchronizeManaAmountS2CPacket::new); - - public SynchronizeManaAmountS2CPacket(PacketByteBuf buf) { - this(buf.readVarInt()); - } - @Override - public void write(PacketByteBuf buf) { - buf.writeVarInt(mana); - } - - @Override - public PacketType getType() { - return PACKET_TYPE; - } - - public static void receive(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - runSyncWithBuf(client, buf, () -> client.player.getManaManager().receiveSync(client, handler, buf, responseSender)); - } - - public static Identifier getId() { - return PACKET_TYPE.getId(); - } -} diff --git a/src/main/java/dev/louis/nebula/networking/UpdateSpellCastabilityS2CPacket.java b/src/main/java/dev/louis/nebula/networking/UpdateSpellCastabilityS2CPacket.java index 5153c79..7320fbc 100644 --- a/src/main/java/dev/louis/nebula/networking/UpdateSpellCastabilityS2CPacket.java +++ b/src/main/java/dev/louis/nebula/networking/UpdateSpellCastabilityS2CPacket.java @@ -1,68 +1,46 @@ package dev.louis.nebula.networking; import dev.louis.nebula.Nebula; -import dev.louis.nebula.spell.Spell; -import dev.louis.nebula.spell.SpellType; +import dev.louis.nebula.api.spell.Spell; +import dev.louis.nebula.api.spell.SpellType; import net.fabricmc.fabric.api.networking.v1.FabricPacket; -import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketType; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; import java.util.HashMap; import java.util.Map; -import static dev.louis.nebula.NebulaClient.runSyncWithBuf; - public record UpdateSpellCastabilityS2CPacket(Map, Boolean> spells) implements FabricPacket { - public static final PacketType PACKET_TYPE = PacketType.create(new Identifier(Nebula.MOD_ID, "updatespellcastability"), UpdateSpellCastabilityS2CPacket::new); + public static final Identifier ID = new Identifier(Nebula.MOD_ID, "updatespellcastability"); + public static final PacketType TYPE = PacketType.create(ID, UpdateSpellCastabilityS2CPacket::read); - private UpdateSpellCastabilityS2CPacket(PacketByteBuf buf) { - this(readMapFromBuf(buf)); + public static UpdateSpellCastabilityS2CPacket read(PacketByteBuf buf) { + return new UpdateSpellCastabilityS2CPacket(readMapFromBuf(buf)); } - public void write(PacketByteBuf buf) { - buf.writeVarInt(spells.size()); - spells.forEach((spellType, knows) -> { - buf.writeRegistryValue(Nebula.SPELL_REGISTRY, spellType); - buf.writeBoolean(knows); - }); + private static Map, Boolean> readMapFromBuf(PacketByteBuf buf) { + return buf.readMap(HashMap::new, UpdateSpellCastabilityS2CPacket::readSpellType, PacketByteBuf::readBoolean); } - public static UpdateSpellCastabilityS2CPacket create(PlayerEntity player) { - Map, Boolean> map = new HashMap<>(); - Nebula.SPELL_REGISTRY.forEach(spellType -> map.put(spellType, spellType.hasLearned(player))); - return new UpdateSpellCastabilityS2CPacket(map); + private static SpellType readSpellType(PacketByteBuf buf) { + return buf.readRegistryValue(SpellType.REGISTRY); } - public static UpdateSpellCastabilityS2CPacket readBuf(PacketByteBuf buf) { - return new UpdateSpellCastabilityS2CPacket(readMapFromBuf(buf)); + public void write(PacketByteBuf buf) { + buf.writeMap( + spells, + this::writeSpellType, + PacketByteBuf::writeBoolean + ); } - private static Map, Boolean> readMapFromBuf(PacketByteBuf buf) { - Map, Boolean> spells = new HashMap<>(); - int size = buf.readVarInt(); - for (int i = 0; i < size; i++) { - SpellType spellType = buf.readRegistryValue(Nebula.SPELL_REGISTRY); - boolean knows = buf.readBoolean(); - spells.put(spellType, knows); - } - return spells; + public void writeSpellType(PacketByteBuf buf, SpellType spellType) { + buf.writeRegistryValue(SpellType.REGISTRY, spellType); } @Override public PacketType getType() { - return PACKET_TYPE; - } - - public static void receive(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - runSyncWithBuf(client, buf, () -> client.player.getSpellManager().receiveSync(client, handler, buf, responseSender)); - } - - public static Identifier getID() { - return PACKET_TYPE.getId(); + return TYPE; } } diff --git a/src/main/java/dev/louis/nebula/spell/Spell.java b/src/main/java/dev/louis/nebula/spell/Spell.java deleted file mode 100644 index 4bd8b81..0000000 --- a/src/main/java/dev/louis/nebula/spell/Spell.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.louis.nebula.spell; - -import dev.louis.nebula.spell.manager.SpellManager; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.util.Identifier; - -/** - * This class represents an attempt to cast a spell. It holds a reference to the caster of the Spell. - */ -public abstract class Spell { - private final SpellType spellType; - private final PlayerEntity caster; - - - public Spell(SpellType spellType, PlayerEntity caster) { - this.spellType = spellType; - this.caster = caster; - } - - /** - * Is called after {@link Spell#isCastable()} if the return of the method is true. - * - * This should not be called manually. - * Use * {@link SpellManager#cast(Spell)} or {@link SpellManager#cast(SpellType)} - */ - public abstract void cast(); - - public Identifier getID() { - return this.getType().getId(); - } - - public PlayerEntity getCaster() { - return this.caster; - } - - public SpellType getType() { - return this.spellType; - } - - /** - * If true {@link Spell#cast()} and {@link Spell#drainMana()} will be called in that order.
- * If false nothing will be called. - */ - public boolean isCastable() { - return this.getType().isCastable(this.caster); - } - - /** - * Called on server.
- * Read additional data about the spell from the buf. - * @param buf The buf to be read from. - * @return The buf after being read from. - */ - public PacketByteBuf readBuf(PacketByteBuf buf) { - return buf; - } - - /** - * Called on client.
- * Write additional data about the spell to the buf. - * @param buf The buf to be written to. - * @return The buf after being written to. - */ - public PacketByteBuf writeBuf(PacketByteBuf buf) { - return buf; - } - - /** - * Drains the amount of Mana required by the SpellType. - */ - public void drainMana() { - getCaster().getManaManager().drainMana(getType().getManaCost()); - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + "[" + this.getID().toString() + "]"; - } -} diff --git a/src/main/java/dev/louis/nebula/spell/SpellType.java b/src/main/java/dev/louis/nebula/spell/SpellType.java deleted file mode 100644 index f52535f..0000000 --- a/src/main/java/dev/louis/nebula/spell/SpellType.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.louis.nebula.spell; - -import dev.louis.nebula.Nebula; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.registry.Registry; -import net.minecraft.util.Identifier; - -import java.util.Optional; - -public class SpellType { - private final SpellFactory factory; - private final int manaCost; - private final boolean needLearning; - - public SpellType(SpellFactory factory, int manaCost, boolean needLearning) { - this.factory = factory; - this.manaCost = manaCost; - this.needLearning = needLearning; - } - - public static void init() { - } - - public static SpellType register(Identifier id, Builder type) { - return Registry.register(Nebula.SPELL_REGISTRY, id, type.build()); - } - - public static Optional> get(Identifier id) { - return Nebula.SPELL_REGISTRY.getOrEmpty(id); - } - - public Identifier getId() { - return Nebula.SPELL_REGISTRY.getId(this); - } - - public boolean isCastable(PlayerEntity player) { - return player.getSpellManager().isCastable(this); - } - - public boolean needsLearning() { - return needLearning; - } - - public boolean hasLearned(PlayerEntity player) { - return player.getSpellManager().hasLearned(this); - } - - public int getManaCost() { - return manaCost; - } - - public T create(PlayerEntity caster) { - return this.factory.create(this, caster); - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + "{id=" + this.getId() + ", manaCost=" + this.getManaCost() + "}"; - } - - public static class Builder { - private final SpellFactory factory; - private final int manaCost; - private boolean needsLearning = true; - - private Builder(SpellFactory factory, int manaCost) { - this.factory = factory; - this.manaCost = manaCost; - } - - public static Builder create(SpellFactory factory, int manaCost) { - return new Builder<>(factory, manaCost); - } - - public Builder needsLearning(boolean needsLearning) { - this.needsLearning = needsLearning; - return this; - } - - public SpellType build() { - return new SpellType<>(this.factory, manaCost, needsLearning); - } - } - - public interface SpellFactory { - T create(SpellType spellType, PlayerEntity caster); - } -} diff --git a/src/main/java/dev/louis/nebula/spell/TickingSpell.java b/src/main/java/dev/louis/nebula/spell/TickingSpell.java deleted file mode 100644 index 1514350..0000000 --- a/src/main/java/dev/louis/nebula/spell/TickingSpell.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.louis.nebula.spell; - -import net.minecraft.entity.player.PlayerEntity; - -public class TickingSpell extends Spell { - protected int spellAge = 0; - private boolean shouldContinue = true; - public TickingSpell(SpellType spellType, PlayerEntity caster) { - super(spellType, caster); - } - - @Override - public void cast() { - this.getCaster().getSpellManager().startTickingSpell(this); - } - - /** - * This method is called every tick while shouldContinue() is true. - * When the Spell is finished, call stop(). This will result in the Spell no longer getting Ticked. - */ - public void tick() { - spellAge++; - } - - /** - * This method stops the spell! - * The Spell will no longer be ticked. - * When overriding this method, make sure to call super.stop(). - * @param fromDeath If the cause of stopping was the death of the Caster. - */ - public void stop(boolean fromDeath) { - shouldContinue = false; - } - - /** - * This method stops the spell! - * The Spell will no longer be ticked. - * Calling this method assumes the Caster is still alive. - * If that is not the case call {@link #stop(boolean)}. - */ - public void stop() { - stop(false); - } - - public boolean shouldContinue() { - return shouldContinue; - } -} diff --git a/src/main/java/dev/louis/nebula/spell/manager/NebulaSpellManager.java b/src/main/java/dev/louis/nebula/spell/manager/NebulaSpellManager.java deleted file mode 100644 index 3d26902..0000000 --- a/src/main/java/dev/louis/nebula/spell/manager/NebulaSpellManager.java +++ /dev/null @@ -1,194 +0,0 @@ -package dev.louis.nebula.spell.manager; - -import dev.louis.nebula.Nebula; -import dev.louis.nebula.event.SpellCastCallback; -import dev.louis.nebula.networking.SpellCastC2SPacket; -import dev.louis.nebula.networking.UpdateSpellCastabilityS2CPacket; -import dev.louis.nebula.spell.Spell; -import dev.louis.nebula.spell.SpellType; -import dev.louis.nebula.spell.TickingSpell; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.entity.damage.DamageSource; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtList; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Identifier; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class NebulaSpellManager implements SpellManager { - protected static final String SPELL_NBT_KEY = "Spell"; - protected static final String SPELLS_NBT_KEY = "Spells"; - - protected final Set tickingSpells = new HashSet<>(); - protected final Set> castableSpells = new HashSet<>(); - protected PlayerEntity player; - - public NebulaSpellManager(PlayerEntity player) { - this.player = player; - } - - @Override - public void tick() { - this.tickingSpells.removeIf(tickingSpell -> { - boolean willBeRemoved = !tickingSpell.shouldContinue(); - if(willBeRemoved) tickingSpell.stop(); - return willBeRemoved; - }); - for (TickingSpell tickingSpell : this.tickingSpells) { - tickingSpell.tick(); - } - } - - @Override - public boolean startTickingSpell(TickingSpell tickingSpell) { - return this.tickingSpells.add(tickingSpell); - } - - @Override - public boolean stopTickingSpell(TickingSpell tickingSpell) { - tickingSpell.stop(false); - return this.tickingSpells.remove(tickingSpell); - } - - @Override - public boolean isSpellTypeTicking(SpellType spellType) { - return this.tickingSpells.stream().anyMatch(tickingSpell -> tickingSpell.getType().equals(spellType)); - } - - @Override - public boolean isSpellTicking(TickingSpell tickingSpell) { - return this.tickingSpells.contains(tickingSpell); - } - - - @Override - public boolean learnSpell(SpellType spellType) { - boolean shouldSync = this.castableSpells.add(spellType); - if(shouldSync) this.sendSync(); - return true; - } - - @Override - public boolean forgetSpell(SpellType spellType) { - boolean shouldSync = this.castableSpells.remove(spellType); - if(shouldSync) this.sendSync(); - return true; - } - - protected Set> getCastableSpells() { - return castableSpells; - } - - protected void updateCastableSpell(Map, Boolean> castableSpells) { - castableSpells.forEach((spellType, knows) -> { - if (knows) this.castableSpells.add(spellType); - else this.castableSpells.remove(spellType); - }); - this.sendSync(); - } - - @Override - public void cast(SpellType spellType) { - this.cast(spellType.create(this.player)); - } - - @Override - public void cast(Spell spell) { - if(SpellCastCallback.EVENT.invoker().interact(this.player, spell) != ActionResult.PASS) return; - if(spell.isCastable()) { - if(this.isServer()) { - spell.drainMana(); - spell.cast(); - } else { - ClientPlayNetworking.send(new SpellCastC2SPacket(spell)); - } - } - } - - @Override - public void onDeath(DamageSource damageSource) { - if(this.isServer()) { - this.tickingSpells.forEach((tickingSpell -> tickingSpell.stop(true))); - } - this.castableSpells.clear(); - this.tickingSpells.clear(); - } - - @Override - public boolean isCastable(SpellType spellType) { - return player.getManaManager().getMana() - spellType.getManaCost() >= 0 && (!spellType.needsLearning() || this.hasLearned(spellType)); - } - - @Override - public boolean hasLearned(SpellType spellType) { - return this.castableSpells.contains(spellType); - } - - @Override - public boolean sendSync() { - if(this.player instanceof ServerPlayerEntity serverPlayerEntity && serverPlayerEntity.networkHandler != null) { - Map, Boolean> castableSpells = new HashMap<>(); - Nebula.SPELL_REGISTRY.forEach(spellType -> castableSpells.put(spellType, this.hasLearned(spellType))); - ServerPlayNetworking.send(serverPlayerEntity, new UpdateSpellCastabilityS2CPacket(castableSpells)); - return true; - } - return false; - } - - @Override - public boolean receiveSync(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - if(this.isServer()) { - Nebula.LOGGER.error("Called receiveSync on server side!"); - return false; - } - UpdateSpellCastabilityS2CPacket packet = UpdateSpellCastabilityS2CPacket.readBuf(buf); - MinecraftClient.getInstance().executeSync(() -> this.updateCastableSpell(packet.spells())); - return true; - } - - @Override - public void writeNbt(NbtCompound nbt) { - NbtList nbtList = new NbtList(); - for (SpellType spell : getCastableSpells()) { - NbtCompound nbtCompound = new NbtCompound(); - nbtCompound.putString(SPELL_NBT_KEY, spell.getId().toString()); - - nbtList.add(nbtCompound); - } - NbtCompound nebulaNbt = nbt.getCompound(Nebula.MOD_ID); - nebulaNbt.put(SPELLS_NBT_KEY, nbtList); - nbt.put(Nebula.MOD_ID, nebulaNbt); - } - - @Override - public void readNbt(NbtCompound nbt) { - NbtList nbtList = (NbtList) nbt.getCompound(Nebula.MOD_ID).get(SPELLS_NBT_KEY); - if(nbtList == null)return; - for (int x = 0; x < nbtList.size(); ++x) { - NbtCompound nbtCompound = nbtList.getCompound(x); - Identifier spell = new Identifier(nbtCompound.getString(SPELL_NBT_KEY)); - SpellType.get(spell).ifPresent(castableSpells::add); - } - } - - @Override - public SpellManager setPlayer(PlayerEntity player) { - this.player = player; - return this; - } - - public boolean isServer() { - return !player.getWorld().isClient(); - } -} diff --git a/src/main/resources/Nebula.mixins.json b/src/main/resources/Nebula.mixins.json index 7f94cac..9ad8dfb 100644 --- a/src/main/resources/Nebula.mixins.json +++ b/src/main/resources/Nebula.mixins.json @@ -8,6 +8,7 @@ "ServerPlayerEntityMixin" ], "client": [ + "ClientPlayerEntityAccessor", "ClientPlayerEntityMixin", "ClientPlayNetworkHandlerMixin" ], diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7f36330..f275749 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -23,9 +23,9 @@ "Nebula.mixins.json" ], "depends": { - "fabricloader": ">=0.15.0", + "fabricloader": ">=0.15.3", "fabric": "*", - "minecraft": ">=1.20.2" + "minecraft": "${supported_version}" }, "custom": { "loom:injected_interfaces": {