diff --git a/src/main/java/com/minelittlepony/unicopia/Debug.java b/src/main/java/com/minelittlepony/unicopia/Debug.java index 581e5e28..b160070d 100644 --- a/src/main/java/com/minelittlepony/unicopia/Debug.java +++ b/src/main/java/com/minelittlepony/unicopia/Debug.java @@ -2,5 +2,4 @@ package com.minelittlepony.unicopia; public interface Debug { boolean DEBUG_SPELLBOOK_CHAPTERS = Boolean.getBoolean("unicopia.debug.spellbookChapters"); - boolean DEBUG_COMMANDS = Boolean.getBoolean("unicopia.debug.commands"); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java index add82ba7..58b1458a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java @@ -19,6 +19,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.util.RegistryUtils; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; @@ -26,15 +29,20 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.Util; import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.tag.FluidTags; +import net.minecraft.server.command.ServerCommandSource; public final class SpellType implements Affine, SpellPredicate { public static final Identifier EMPTY_ID = Unicopia.id("none"); public static final SpellType EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, SpellTraits.EMPTY, t -> null); public static final Registry> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells")); + public static final RegistryKey>> REGISTRY_KEY = REGISTRY.getKey(); public static final Map>> BY_AFFINITY = new EnumMap<>(Affinity.class); + private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id)); + public static final SpellType PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, PlaceableSpell::new); public static final SpellType THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, ThrowableSpell::new); @@ -192,6 +200,11 @@ public final class SpellType implements Affine, SpellPredicate< return BY_AFFINITY.computeIfAbsent(affinity, a -> new LinkedHashSet<>()); } + public static SpellType fromArgument(CommandContext context, String name) throws CommandSyntaxException { + Identifier id = context.getArgument(name, RegistryKey.class).getValue(); + return REGISTRY.getOrEmpty(id).orElseThrow(() -> UNKNOWN_SPELL_TYPE_EXCEPTION.create(id)); + } + public interface Factory { T create(CustomisedSpellType type); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java index aca7e161..13881e53 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java @@ -268,7 +268,11 @@ public final class SpellTraits implements Iterable> { } public static Optional fromString(String traits) { - return fromEntries(Arrays.stream(traits.split(" ")).map(a -> a.split(":")).map(pair -> { + return fromString(traits, " "); + } + + public static Optional fromString(String traits, String delimiter) { + return fromEntries(Arrays.stream(traits.split(delimiter)).map(a -> a.split(":")).map(pair -> { Trait key = Trait.fromName(pair[0]).orElse(null); if (key == null) { Unicopia.LOGGER.warn("Skipping unknown trait {}", pair[0]); diff --git a/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java b/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java new file mode 100644 index 00000000..7ab5fca8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java @@ -0,0 +1,132 @@ +package com.minelittlepony.unicopia.command; + +import java.util.Optional; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.entity.CastSpellEntity; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.argument.BlockPosArgumentType; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.command.argument.RegistryKeyArgumentType; +import net.minecraft.command.argument.RotationArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +public class CastCommand { + public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registries) { + dispatcher.register( + CommandManager.literal("cast").requires(s -> s.hasPermissionLevel(2)) + .then( + buildBranches( + CommandManager.argument("spell", RegistryKeyArgumentType.registryKey(SpellType.REGISTRY_KEY)), + c -> Optional.empty() + ) + ) + .then( + CommandManager.argument("spell", RegistryKeyArgumentType.registryKey(SpellType.REGISTRY_KEY)) + .then(buildBranches( + CommandManager.argument("traits", TraitsArgumentType.traits()), + CastCommand::getTraits + )) + ) + ); + } + + private static ArgumentBuilder buildBranches(ArgumentBuilder builder, + TraitsFunc traitsFunc) { + return builder.then( + CommandManager.literal("throw").then( + CommandManager.argument("rot", RotationArgumentType.rotation()).executes(c -> thrown(c, traitsFunc, 1.5F)).then( + CommandManager.argument("speed", FloatArgumentType.floatArg(0, 10)).executes(c -> thrown(c, + traitsFunc, + FloatArgumentType.getFloat(c, "speed") + )) + ) + ) + ) + .then( + CommandManager.literal("place").executes(c -> placed(c, traitsFunc, Optional.empty(), c.getSource().getRotation())).then( + CommandManager.argument("loc", BlockPosArgumentType.blockPos()).executes(c -> placed(c, + traitsFunc, + Optional.of(BlockPosArgumentType.getBlockPos(c, "location").toCenterPos()), + c.getSource().getRotation() + )).then( + CommandManager.argument("rot", RotationArgumentType.rotation()).executes(c -> placed(c, + traitsFunc, + Optional.of(BlockPosArgumentType.getBlockPos(c, "location").toCenterPos()), + RotationArgumentType.getRotation(c, "rot").toAbsoluteRotation(c.getSource()) + )) + ) + ) + ) + .then( + CommandManager.literal("apply").then( + CommandManager.argument("targets", EntityArgumentType.entities()).executes(c -> apply(c, traitsFunc)) + ) + ); + } + + private static Optional getTraits(CommandContext source) throws CommandSyntaxException { + return Optional.of(TraitsArgumentType.getSpellTraits(source, "traits")); + } + + private static CustomisedSpellType getSpell(CommandContext source, TraitsFunc traits) throws CommandSyntaxException { + SpellType spellType = SpellType.fromArgument(source, "spell"); + return spellType.withTraits(traits.getTraits(source).orElse(spellType.getTraits())); + } + + private static int thrown(CommandContext source, TraitsFunc traits, float speed) throws CommandSyntaxException { + ServerPlayerEntity player = source.getSource().getPlayerOrThrow(); + getSpell(source, traits).create().toThrowable().throwProjectile(Caster.of(player).orElseThrow()).ifPresent(projectile -> { + Vec2f rotation = RotationArgumentType.getRotation(source, "rot").toAbsoluteRotation(source.getSource()); + projectile.setVelocity(player, rotation.x, rotation.y, 0, speed, 1); + }); + + return 0; + } + + private static int placed(CommandContext source, TraitsFunc traits, Optional position, Vec2f rotation) throws CommandSyntaxException { + ServerPlayerEntity player = source.getSource().getPlayerOrThrow(); + PlaceableSpell spell = getSpell(source, traits).create().toPlaceable(); + Caster caster = Caster.of(player).orElseThrow(); + + spell.setOrientation(rotation.x, rotation.y); + spell.apply(caster); + + position.ifPresent(pos -> { + spell.tick(caster, Situation.BODY); + CastSpellEntity entity = spell.getSpellEntity(caster).orElseThrow(); + entity.setPosition(pos); + }); + + + return 0; + } + + private static int apply(CommandContext source, TraitsFunc traits) throws CommandSyntaxException { + CustomisedSpellType spellType = getSpell(source, traits); + EntityArgumentType.getEntities(source, "targets").forEach(target -> { + Caster.of(target).ifPresent(caster -> spellType.apply(caster)); + }); + + return 0; + } + + interface TraitsFunc { + Optional getTraits(CommandContext source) throws CommandSyntaxException; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/command/Commands.java b/src/main/java/com/minelittlepony/unicopia/command/Commands.java index 2765af5f..739ec65a 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/Commands.java +++ b/src/main/java/com/minelittlepony/unicopia/command/Commands.java @@ -2,11 +2,11 @@ package com.minelittlepony.unicopia.command; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import com.minelittlepony.unicopia.Debug; import com.minelittlepony.unicopia.Unicopia; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; import net.minecraft.server.MinecraftServer; public class Commands { @@ -17,21 +17,22 @@ public class Commands { EnumArgumentType.class, new EnumArgumentType.Serializer() ); - CommandRegistrationCallback.EVENT.register((dispatcher, access, environment) -> { + ArgumentTypeRegistry.registerArgumentType( + Unicopia.id("spell_traits"), + TraitsArgumentType.class, + ConstantArgumentSerializer.of(TraitsArgumentType::traits) + ); + CommandRegistrationCallback.EVENT.register((dispatcher, registries, environment) -> { RacelistCommand.register(dispatcher); EmoteCommand.register(dispatcher); - if (Unicopia.getConfig().enableCheats.get() || environment.dedicated) { - SpeciesCommand.register(dispatcher, environment); - WorldTribeCommand.register(dispatcher); - } - if (Unicopia.getConfig().enableCheats.get()) { - GravityCommand.register(dispatcher); - DisguiseCommand.register(dispatcher, access); - if (Debug.DEBUG_COMMANDS) { - TraitCommand.register(dispatcher); - ManaCommand.register(dispatcher); - } - } + SpeciesCommand.register(dispatcher, environment); + WorldTribeCommand.register(dispatcher); + + GravityCommand.register(dispatcher); + DisguiseCommand.register(dispatcher, registries); + CastCommand.register(dispatcher, registries); + TraitCommand.register(dispatcher); + ManaCommand.register(dispatcher); }); if (FabricLoader.getInstance().getGameInstance() instanceof MinecraftServer server) { diff --git a/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java b/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java index eabd4789..61607b62 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java @@ -22,7 +22,7 @@ class GravityCommand { static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder builder = CommandManager .literal("gravity") - .requires(s -> s.hasPermissionLevel(4)); + .requires(s -> s.hasPermissionLevel(2)); builder.then(CommandManager.literal("get") .executes(context -> get(context.getSource(), context.getSource().getPlayer(), true)) diff --git a/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java b/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java index 3bc9725f..d3204431 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/ManaCommand.java @@ -13,7 +13,7 @@ import net.minecraft.text.Text; public class ManaCommand { static void register(CommandDispatcher dispatcher) { dispatcher.register(CommandManager - .literal("mana") + .literal("mana").requires(s -> s.hasPermissionLevel(2)) .then(CommandManager.argument("type", EnumArgumentType.of(ManaType.class)).executes(source -> { var type = source.getArgument("type", ManaType.class); var pony = Pony.of(source.getSource().getPlayer()); diff --git a/src/main/java/com/minelittlepony/unicopia/command/TraitCommand.java b/src/main/java/com/minelittlepony/unicopia/command/TraitCommand.java index 828afe6e..2c02dc71 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/TraitCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/TraitCommand.java @@ -19,7 +19,7 @@ class TraitCommand { static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder builder = CommandManager .literal("trait") - .requires(s -> s.hasPermissionLevel(4)); + .requires(s -> s.hasPermissionLevel(2)); builder.then(CommandManager.literal("add") .then(CommandManager.argument("trait", EnumArgumentType.of(Trait.class)) diff --git a/src/main/java/com/minelittlepony/unicopia/command/TraitsArgumentType.java b/src/main/java/com/minelittlepony/unicopia/command/TraitsArgumentType.java new file mode 100644 index 00000000..3f055a93 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/command/TraitsArgumentType.java @@ -0,0 +1,102 @@ +package com.minelittlepony.unicopia.command; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import net.minecraft.text.Text; + +class TraitsArgumentType implements ArgumentType { + private static final List EXAMPLES = List.of("strength:1,focus:2", ""); + public static final SimpleCommandExceptionType UNRECOGNISED_TRAIT_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.spell_trait.unrecognised")); + + private TraitsArgumentType() { + + } + + public static TraitsArgumentType traits() { + return new TraitsArgumentType(); + } + + public static SpellTraits getSpellTraits(CommandContext context, String name) { + return context.getArgument(name, SpellTraits.class); + } + + @Override + public SpellTraits parse(StringReader reader) throws CommandSyntaxException { + if (!reader.canRead()) { + return SpellTraits.EMPTY; + } + + SpellTraits.Builder builder = new SpellTraits.Builder(); + while (reader.canRead() && reader.peek() != ' ') { + Trait trait = Trait.fromName(readTraitName(reader)).orElseThrow(() -> UNRECOGNISED_TRAIT_EXCEPTION.createWithContext(reader)); + reader.expect(':'); + float value = reader.readFloat(); + builder.with(trait, value); + if (!reader.canRead() || reader.peek() != ',') { + break; + } + reader.skip(); + } + + return builder.build(); + } + + private String readTraitName(StringReader reader) { + StringBuilder builder = new StringBuilder(); + while (reader.canRead() && reader.peek() != ' ' && reader.peek() != ':') { + builder.append(reader.read()); + } + + return builder.toString(); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + String input = builder.getRemaining().toLowerCase(Locale.ROOT); + if (input.indexOf(' ') != -1) { + return Suggestions.empty(); + } + + int colonIndex = input.lastIndexOf(':'); + int commaIndex = Math.max(input.lastIndexOf(','), colonIndex); + if (commaIndex > -1) { + builder = builder.createOffset(builder.getStart() + commaIndex + 1); + input = input.substring(commaIndex + 1, input.length()); + + if (commaIndex == colonIndex) { + return Suggestions.empty(); + } + } + + final String incomplete = input; + System.out.println(incomplete); + Trait.all().stream() + .map(trait -> trait.name().toLowerCase(Locale.ROOT)) + .filter(trait -> incomplete.isBlank() || trait.startsWith(incomplete)) + .forEach(builder::suggest); + + return builder.buildFuture(); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + @Override + public String toString() { + return "SpellTraits()"; + } +} \ No newline at end of file