Added a /cast command #102

This commit is contained in:
Sollace 2023-04-30 20:26:31 +01:00
parent b14e5c0d23
commit e2ffdd43e5
9 changed files with 270 additions and 19 deletions

View file

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

View file

@ -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<T extends Spell> implements Affine, SpellPredicate<T> {
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<SpellType<?>> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells"));
public static final RegistryKey<? extends Registry<SpellType<?>>> REGISTRY_KEY = REGISTRY.getKey();
public static final Map<Affinity, Set<SpellType<?>>> 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<PlaceableSpell> PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, PlaceableSpell::new);
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, ThrowableSpell::new);
@ -192,6 +200,11 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return BY_AFFINITY.computeIfAbsent(affinity, a -> new LinkedHashSet<>());
}
public static SpellType<?> fromArgument(CommandContext<ServerCommandSource> 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 extends Spell> {
T create(CustomisedSpellType<T> type);
}

View file

@ -268,7 +268,11 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
}
public static Optional<SpellTraits> fromString(String traits) {
return fromEntries(Arrays.stream(traits.split(" ")).map(a -> a.split(":")).map(pair -> {
return fromString(traits, " ");
}
public static Optional<SpellTraits> 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]);

View file

@ -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<ServerCommandSource> 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<ServerCommandSource, ?> buildBranches(ArgumentBuilder<ServerCommandSource, ?> 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<SpellTraits> getTraits(CommandContext<ServerCommandSource> source) throws CommandSyntaxException {
return Optional.of(TraitsArgumentType.getSpellTraits(source, "traits"));
}
private static CustomisedSpellType<?> getSpell(CommandContext<ServerCommandSource> 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<ServerCommandSource> 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<ServerCommandSource> source, TraitsFunc traits, Optional<Vec3d> 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<ServerCommandSource> 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<SpellTraits> getTraits(CommandContext<ServerCommandSource> source) throws CommandSyntaxException;
}
}

View file

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

View file

@ -22,7 +22,7 @@ class GravityCommand {
static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
LiteralArgumentBuilder<ServerCommandSource> 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))

View file

@ -13,7 +13,7 @@ import net.minecraft.text.Text;
public class ManaCommand {
static void register(CommandDispatcher<ServerCommandSource> 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());

View file

@ -19,7 +19,7 @@ class TraitCommand {
static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
LiteralArgumentBuilder<ServerCommandSource> 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))

View file

@ -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<SpellTraits> {
private static final List<String> 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 <S> SpellTraits getSpellTraits(CommandContext<S> 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 <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> 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<String> getExamples() {
return EXAMPLES;
}
@Override
public String toString() {
return "SpellTraits()";
}
}