diff --git a/README.md b/README.md index 903a547..e09b3ed 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ You can add the library by inserting the following in your `build.gradle` : ```gradle repositories { - maven { - name = 'Ladysnake Libs' - url = 'https://dl.bintray.com/ladysnake/libs' + maven { + name = "Ladysnake Libs" + url = 'https://dl.bintray.com/ladysnake/libs' } } @@ -37,6 +37,9 @@ You can find the current version of PAL in the [releases](https://github.com/Lad You can find a couple examples in the [Test Mod](https://github.com/Ladysnake/PlayerAbilityLib/tree/master/src/testmod/java/io/github/ladysnake/paltest). +Note that player abilities can only accessed serverside. If you want to store more complex data, or to synchronize it between server and client, +you should take a look at [Cardinal Components API](https://github.com/OnyxStudios/Cardinal-Components-API). + [Item that toggles an ability](https://github.com/Ladysnake/PlayerAbilityLib/blob/master/src/testmod/java/io/github/ladysnake/paltest/AbilityToggleItem.java) : ```java public static final AbilitySource FLIGHT_CHARM = Pal.getAbilitySource("mymod", "flight_charm"); // works like an identifier diff --git a/build.gradle b/build.gradle index 9bda730..cb55eb8 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ dependencies { mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - api "org.jetbrains:annotations:17.0.0" + api "org.jetbrains:annotations:19.0.0" testmodCompile sourceSets.main.output } diff --git a/src/main/java/io/github/ladysnake/pal/AbilitySource.java b/src/main/java/io/github/ladysnake/pal/AbilitySource.java index 8513f91..838a88d 100644 --- a/src/main/java/io/github/ladysnake/pal/AbilitySource.java +++ b/src/main/java/io/github/ladysnake/pal/AbilitySource.java @@ -80,13 +80,21 @@ public boolean grants(PlayerEntity player, PlayerAbility ability) { return ability.getTracker(player).isGrantedBy(this); } + /** + * Returns the identifier used to create this {@code AbilitySource}. + * + *

The returned identifier is unique and can be passed to {@link Pal#getAbilitySource(Identifier)} + * to retrieve this instance. + * + * @return the identifier wrapped by this {@code AbilitySource} + */ public Identifier getId() { return this.id; } @Override public String toString() { - return "AbilitySource[" + this.id + "]"; + return "AbilitySource@" + this.id; } } diff --git a/src/main/java/io/github/ladysnake/pal/AbilityTracker.java b/src/main/java/io/github/ladysnake/pal/AbilityTracker.java index cc25587..5bfea40 100644 --- a/src/main/java/io/github/ladysnake/pal/AbilityTracker.java +++ b/src/main/java/io/github/ladysnake/pal/AbilityTracker.java @@ -18,9 +18,13 @@ package io.github.ladysnake.pal; import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.Contract; /** * A tracker for a player ability that can be turned on or off. + * + * @apiNote this interface is intended to be implemented by API consumers that + * provide new {@linkplain PlayerAbility abilities}. */ public interface AbilityTracker { @@ -32,6 +36,7 @@ public interface AbilityTracker { * * @param abilitySource the source granting the ability */ + @Contract(mutates = "this") void addSource(AbilitySource abilitySource); /** @@ -42,6 +47,7 @@ public interface AbilityTracker { * * @param abilitySource the source granting the ability */ + @Contract(mutates = "this") void removeSource(AbilitySource abilitySource); /** @@ -50,6 +56,7 @@ public interface AbilityTracker { * @param abilitySource the source granting the ability * @return {@code true} if this tracker's ability is provided by {@code abilitySource} */ + @Contract(pure = true) boolean isGrantedBy(AbilitySource abilitySource); /** @@ -60,6 +67,7 @@ public interface AbilityTracker { * * @return {@code true} if this ability is enabled */ + @Contract(pure = true) boolean isEnabled(); /** @@ -78,6 +86,7 @@ public interface AbilityTracker { * * @param tag the tag to write to */ + @Contract(mutates = "param") void save(CompoundTag tag); /** @@ -85,5 +94,6 @@ public interface AbilityTracker { * * @param tag the tag to read from */ + @Contract(mutates = "this") void load(CompoundTag tag); } diff --git a/src/main/java/io/github/ladysnake/pal/PlayerAbility.java b/src/main/java/io/github/ladysnake/pal/PlayerAbility.java index ad29de1..81c1d2c 100644 --- a/src/main/java/io/github/ladysnake/pal/PlayerAbility.java +++ b/src/main/java/io/github/ladysnake/pal/PlayerAbility.java @@ -22,6 +22,8 @@ import net.fabricmc.fabric.api.event.Event; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; import java.util.function.BiFunction; @@ -41,6 +43,7 @@ * @see AbilityTracker */ public final class PlayerAbility { + @ApiStatus.Internal final Event updateEvent = PalInternals.createUpdateEvent(); private final BiFunction trackerFactory; private final Identifier id; @@ -49,6 +52,7 @@ public final class PlayerAbility { * @see Pal#registerAbility(String, String, BiFunction) * @see Pal#registerAbility(Identifier, BiFunction) */ + @ApiStatus.Internal PlayerAbility(Identifier id, BiFunction trackerFactory) { this.id = id; this.trackerFactory = trackerFactory; @@ -71,16 +75,35 @@ public boolean isEnabledFor(PlayerEntity player) { return this.getTracker(player).isEnabled(); } + /** + * Creates an {@code AbilityTracker} that can be attached to {@code player}. + * + *

This method is called for every registered {@code PlayerAbility} + * when a new player is instantiated. It is not intended to be called from consumer code. + * + * @param player the player to create a tracker for. + * @return a new {@code AbilityTracker} for {@code player}. + */ + @Contract("_ -> new") + @ApiStatus.Internal public AbilityTracker createTracker(PlayerEntity player) { return this.trackerFactory.apply(this, player); } + /** + * Returns the identifier used to register this {@code PlayerAbility}. + * + *

The returned identifier is unique and can be passed to {@link Pal#provideRegisteredAbility(Identifier)} + * to retrieve this instance. + * + * @return the identifier wrapped by this {@code PlayerAbility} + */ public Identifier getId() { return this.id; } @Override public String toString() { - return "PlayerAbility[" + this.id + "]"; + return "PlayerAbility@" + this.id; } } diff --git a/src/main/java/io/github/ladysnake/pal/PlayerAbilityEnableCallback.java b/src/main/java/io/github/ladysnake/pal/PlayerAbilityEnableCallback.java index ae1f525..a315d7d 100644 --- a/src/main/java/io/github/ladysnake/pal/PlayerAbilityEnableCallback.java +++ b/src/main/java/io/github/ladysnake/pal/PlayerAbilityEnableCallback.java @@ -21,9 +21,13 @@ import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.entity.player.PlayerEntity; +/** + * Callback interface for receiving ability enabling events. + * + * @see PlayerAbilityUpdatedCallback + */ @FunctionalInterface public interface PlayerAbilityEnableCallback { - Event EVENT = EventFactory.createArrayBacked(PlayerAbilityEnableCallback.class, (listeners) -> (player, abilityId, abilitySource) -> { for (PlayerAbilityEnableCallback listener : listeners) { @@ -34,5 +38,19 @@ public interface PlayerAbilityEnableCallback { return true; }); - boolean allow(PlayerEntity player, PlayerAbility abilityId, AbilitySource abilitySource); + /** + * Called when an {@code AbilitySource} attempts to enable a {@code PlayerAbility} on a player. + * + *

The callback may return {@code false} to reject the activation, + * keeping the ability in its previous activation state. + * Some abilities may stay {@linkplain PlayerAbility#isEnabledFor(PlayerEntity) enabled} + * despite all sources of activation being rejected, because of intrinsic providers like the player's gamemode. + * + * @param player the affected player + * @param ability the ability being enabled + * @param abilitySource the source of the ability + * @return {@code true} to let {@code abilitySource} enable the ability on {@code player}, + * and {@code false} to prevent the ability from being enabled. + */ + boolean allow(PlayerEntity player, PlayerAbility ability, AbilitySource abilitySource); } diff --git a/src/main/java/io/github/ladysnake/pal/PlayerAbilityUpdatedCallback.java b/src/main/java/io/github/ladysnake/pal/PlayerAbilityUpdatedCallback.java index 90f0451..9a14fd9 100644 --- a/src/main/java/io/github/ladysnake/pal/PlayerAbilityUpdatedCallback.java +++ b/src/main/java/io/github/ladysnake/pal/PlayerAbilityUpdatedCallback.java @@ -20,12 +20,22 @@ import net.fabricmc.fabric.api.event.Event; import net.minecraft.entity.player.PlayerEntity; +/** + * Callback interface for receiving ability update events. + * + * @see PlayerAbilityEnableCallback + */ @FunctionalInterface public interface PlayerAbilityUpdatedCallback { - static Event event(PlayerAbility ability) { return ability.updateEvent; } + /** + * Called when the tracked ability's state gets updated on the given player. + * + * @param player the player on which the ability has been updated + * @param nowEnabled the new value returned by {@link PlayerAbility#isEnabledFor(PlayerEntity)} + */ void onAbilityUpdated(PlayerEntity player, boolean nowEnabled); } diff --git a/src/main/java/io/github/ladysnake/pal/SimpleAbilityTracker.java b/src/main/java/io/github/ladysnake/pal/SimpleAbilityTracker.java index 8a80af2..9696ce6 100644 --- a/src/main/java/io/github/ladysnake/pal/SimpleAbilityTracker.java +++ b/src/main/java/io/github/ladysnake/pal/SimpleAbilityTracker.java @@ -82,15 +82,21 @@ public void refresh(boolean syncVanilla) { } } + /** + * Returns {@code true} if this tracker's ability should be enabled. + * + *

This is independent of the actual value returned by {@link #isEnabled()} + * and may be used to update the latter. + * + * @return {@code true} if the tracked ability should currently be enabled + */ protected boolean shouldBeEnabled() { - boolean enabled = false; for (AbilitySource abilitySource : this.abilitySources) { if (PlayerAbilityEnableCallback.EVENT.invoker().allow(this.player, this.ability, abilitySource)) { - enabled = true; - break; + return true; } } - return enabled; + return false; } @Override diff --git a/src/main/java/io/github/ladysnake/pal/VanillaAbilities.java b/src/main/java/io/github/ladysnake/pal/VanillaAbilities.java index 59cc58f..0120aad 100644 --- a/src/main/java/io/github/ladysnake/pal/VanillaAbilities.java +++ b/src/main/java/io/github/ladysnake/pal/VanillaAbilities.java @@ -29,7 +29,7 @@ public final class VanillaAbilities { /** * If enabled, players become invulnerable* to all damage, like in creative and spectator mode. * - *

Note", " Damage sources that {@link DamageSource#isOutOfWorld() bypass invulnerability} + *

Note: Damage sources that {@link DamageSource#isOutOfWorld() bypass invulnerability} * can still damage players with this ability enabled. * * @see PlayerAbilities#invulnerable diff --git a/src/main/java/io/github/ladysnake/pal/impl/VanillaAbilityTracker.java b/src/main/java/io/github/ladysnake/pal/impl/VanillaAbilityTracker.java index f49d6d7..84d8373 100644 --- a/src/main/java/io/github/ladysnake/pal/impl/VanillaAbilityTracker.java +++ b/src/main/java/io/github/ladysnake/pal/impl/VanillaAbilityTracker.java @@ -25,6 +25,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.GameMode; +import org.jetbrains.annotations.Contract; import java.util.Objects; import java.util.function.Predicate; @@ -83,6 +84,7 @@ private static GameMode getGamemode(PlayerEntity player) { @FunctionalInterface public interface AbilitySetter { + @Contract(mutates = "param2") void set(GameMode g, PlayerAbilities abilities, boolean enabled); } } diff --git a/src/main/java/io/github/ladysnake/pal/impl/package-info.java b/src/main/java/io/github/ladysnake/pal/impl/package-info.java new file mode 100644 index 0000000..f9124d5 --- /dev/null +++ b/src/main/java/io/github/ladysnake/pal/impl/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Internal +package io.github.ladysnake.pal.impl; + +import org.jetbrains.annotations.ApiStatus;