From c20776d6aadf82ff912f4f946cc716e37565ffc6 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 19 Jun 2025 19:53:09 +0100 Subject: [PATCH] Remove Forge capability attachment --- forge/build.gradle | 1 - forge/forge-api/build.gradle | 15 -- .../forge/capabilities/UserCapability.java | 85 ---------- forge/loader/build.gradle | 2 - .../luckperms/forge/ForgeSenderFactory.java | 28 ++-- .../lucko/luckperms/forge/LPForgePlugin.java | 4 - .../capabilities/UserCapabilityImpl.java | 158 ------------------ .../capabilities/UserCapabilityListener.java | 107 ------------ .../forge/context/ForgeContextManager.java | 18 +- .../listeners/ForgeConnectionListener.java | 4 - .../forge/service/ForgePermissionHandler.java | 10 +- .../forge/util/BrigadierInjector.java | 24 +-- settings.gradle | 1 - 13 files changed, 26 insertions(+), 431 deletions(-) delete mode 100644 forge/forge-api/build.gradle delete mode 100644 forge/forge-api/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapability.java delete mode 100644 forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityImpl.java delete mode 100644 forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityListener.java diff --git a/forge/build.gradle b/forge/build.gradle index 30ca7bed5..4b6b18188 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -21,7 +21,6 @@ dependencies { annotationProcessor 'net.minecraftforge:eventbus-validator:7.0-beta.7' implementation project(':common') compileOnly project(':common:loader-utils') - compileOnly project(':forge:forge-api') } shadowJar { diff --git a/forge/forge-api/build.gradle b/forge/forge-api/build.gradle deleted file mode 100644 index e846d9dbd..000000000 --- a/forge/forge-api/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - alias(libs.plugins.forgegradle) -} - -sourceCompatibility = 21 -targetCompatibility = 21 - -minecraft { - mappings channel: 'official', version: minecraftVersion -} - -dependencies { - minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}" - implementation project(':api') -} diff --git a/forge/forge-api/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapability.java b/forge/forge-api/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapability.java deleted file mode 100644 index e40066ed5..000000000 --- a/forge/forge-api/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapability.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.lucko.luckperms.forge.capabilities; - -import net.luckperms.api.query.QueryOptions; -import net.luckperms.api.util.Tristate; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityManager; -import net.minecraftforge.common.capabilities.CapabilityToken; - -/** - * A Forge {@link Capability} that attaches LuckPerms functionality onto {@link ServerPlayer}s. - */ -public interface UserCapability { - - /** - * The identifier used for the capability - */ - ResourceLocation IDENTIFIER = ResourceLocation.fromNamespaceAndPath("luckperms", "user"); - - /** - * The capability instance. - */ - Capability CAPABILITY = CapabilityManager.get(new CapabilityToken(){}); - - /** - * Checks for a permission. - * - * @param permission the permission - * @return the result - */ - default boolean hasPermission(String permission) { - return checkPermission(permission).asBoolean(); - } - - /** - * Runs a permission check. - * - * @param permission the permission - * @return the result - */ - Tristate checkPermission(String permission); - - /** - * Runs a permission check. - * - * @param permission the permission - * @param queryOptions the query options - * @return the result - */ - Tristate checkPermission(String permission, QueryOptions queryOptions); - - /** - * Gets the user's currently query options. - * - * @return the current query options for the user - */ - QueryOptions getQueryOptions(); - -} diff --git a/forge/loader/build.gradle b/forge/loader/build.gradle index 238558f8c..8ecd38e59 100644 --- a/forge/loader/build.gradle +++ b/forge/loader/build.gradle @@ -15,12 +15,10 @@ dependencies { minecraft "net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}" implementation project(':api') implementation project(':common:loader-utils') - implementation project(':forge:forge-api') } build { dependsOn(":forge:build") - dependsOn(":forge:forge-api:build") } jar { diff --git a/forge/src/main/java/me/lucko/luckperms/forge/ForgeSenderFactory.java b/forge/src/main/java/me/lucko/luckperms/forge/ForgeSenderFactory.java index 4545c8930..397cdaf73 100644 --- a/forge/src/main/java/me/lucko/luckperms/forge/ForgeSenderFactory.java +++ b/forge/src/main/java/me/lucko/luckperms/forge/ForgeSenderFactory.java @@ -29,15 +29,15 @@ import com.mojang.brigadier.ParseResults; import com.mojang.serialization.JsonOps; import me.lucko.luckperms.common.cacheddata.result.TristateResult; import me.lucko.luckperms.common.locale.TranslationManager; +import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.query.QueryOptionsImpl; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.sender.SenderFactory; import me.lucko.luckperms.common.verbose.VerboseCheckTarget; import me.lucko.luckperms.common.verbose.event.CheckOrigin; -import me.lucko.luckperms.forge.capabilities.UserCapability; -import me.lucko.luckperms.forge.capabilities.UserCapabilityImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.luckperms.api.query.QueryOptions; import net.luckperms.api.util.Tristate; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; @@ -73,24 +73,22 @@ public class ForgeSenderFactory extends SenderFactory toNativeText(TranslationManager.render(message, locale)), false); } @Override protected Tristate getPermissionValue(CommandSourceStack commandSource, String node) { - if (commandSource.getEntity() instanceof ServerPlayer) { - ServerPlayer player = (ServerPlayer) commandSource.getEntity(); - UserCapability user = UserCapabilityImpl.get(player); - return user.checkPermission(node); + if (commandSource.getEntity() instanceof ServerPlayer player) { + User user = getPlugin().getUserManager().getIfLoaded(player.getUUID()); + if (user == null) { + return Tristate.UNDEFINED; + } + + QueryOptions queryOptions = getPlugin().getContextManager().getQueryOptions(player); + return user.getCachedData().getPermissionData(queryOptions).checkPermission(node, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result(); } VerboseCheckTarget target = VerboseCheckTarget.internal(commandSource.getTextName()); diff --git a/forge/src/main/java/me/lucko/luckperms/forge/LPForgePlugin.java b/forge/src/main/java/me/lucko/luckperms/forge/LPForgePlugin.java index 62e275d30..00725cc82 100644 --- a/forge/src/main/java/me/lucko/luckperms/forge/LPForgePlugin.java +++ b/forge/src/main/java/me/lucko/luckperms/forge/LPForgePlugin.java @@ -41,7 +41,6 @@ import me.lucko.luckperms.common.plugin.AbstractLuckPermsPlugin; import me.lucko.luckperms.common.sender.DummyConsoleSender; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.forge.calculator.ForgeCalculatorFactory; -import me.lucko.luckperms.forge.capabilities.UserCapabilityListener; import me.lucko.luckperms.forge.context.ForgeContextManager; import me.lucko.luckperms.forge.context.ForgePlayerCalculator; import me.lucko.luckperms.forge.listeners.ForgeAutoOpListener; @@ -93,9 +92,6 @@ public class LPForgePlugin extends AbstractLuckPermsPlugin { ForgePlatformListener platformListener = new ForgePlatformListener(this); this.bootstrap.registerListeners(platformListener); - UserCapabilityListener userCapabilityListener = new UserCapabilityListener(this); - this.bootstrap.registerListeners(userCapabilityListener); - ForgePermissionHandlerListener permissionHandlerListener = new ForgePermissionHandlerListener(this); this.bootstrap.registerListeners(permissionHandlerListener); diff --git a/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityImpl.java b/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityImpl.java deleted file mode 100644 index 4e9ca8f6c..000000000 --- a/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.lucko.luckperms.forge.capabilities; - -import me.lucko.luckperms.common.cacheddata.type.PermissionCache; -import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier; -import me.lucko.luckperms.common.locale.TranslationManager; -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.verbose.event.CheckOrigin; -import me.lucko.luckperms.forge.LPForgePlugin; -import net.luckperms.api.query.QueryOptions; -import net.luckperms.api.util.Tristate; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.common.util.LazyOptional; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Locale; - -public class UserCapabilityImpl implements UserCapability { - - private static LazyOptional getCapability(Player player) { - LazyOptional optional = player.getCapability(CAPABILITY); - if (optional.isPresent()) { - return optional; - } - - // if capability is missing, try to restore them before trying again - player.reviveCaps(); - return player.getCapability(CAPABILITY); - } - - /** - * Gets a {@link UserCapability} for a given {@link ServerPlayer}. - * - * @param player the player - * @return the capability - */ - public static @NotNull UserCapabilityImpl get(@NotNull Player player) { - return (UserCapabilityImpl) getCapability(player).orElseThrow(() -> new IllegalStateException("Capability missing for " + player.getUUID())); - } - - /** - * Gets a {@link UserCapability} for a given {@link ServerPlayer}. - * - * @param player the player - * @return the capability, or null - */ - public static @Nullable UserCapabilityImpl getNullable(@NotNull Player player) { - return (UserCapabilityImpl) getCapability(player).resolve().orElse(null); - } - - private boolean initialised = false; - - private User user; - private QueryOptionsSupplier queryOptionsSupplier; - private String language; - private Locale locale; - - public UserCapabilityImpl() { - - } - - public void initialise(UserCapabilityImpl previous, ServerPlayer player, LPForgePlugin plugin) { - this.user = previous.user; - this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player); - this.language = previous.language; - this.locale = previous.locale; - this.initialised = true; - } - - public void initialise(User user, ServerPlayer player, LPForgePlugin plugin) { - this.user = user; - this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player); - this.initialised = true; - } - - private void assertInitialised() { - if (!this.initialised) { - throw new IllegalStateException("Capability has not been initialised"); - } - } - - @Override - public Tristate checkPermission(String permission) { - assertInitialised(); - - if (permission == null) { - throw new NullPointerException("permission"); - } - - return checkPermission(permission, this.queryOptionsSupplier.getQueryOptions()); - } - - @Override - public Tristate checkPermission(String permission, QueryOptions queryOptions) { - assertInitialised(); - - if (permission == null) { - throw new NullPointerException("permission"); - } - - if (queryOptions == null) { - throw new NullPointerException("queryOptions"); - } - - PermissionCache cache = this.user.getCachedData().getPermissionData(queryOptions); - return cache.checkPermission(permission, CheckOrigin.PLATFORM_API_HAS_PERMISSION).result(); - } - - public User getUser() { - assertInitialised(); - return this.user; - } - - @Override - public QueryOptions getQueryOptions() { - return getQueryOptionsSupplier().getQueryOptions(); - } - - public QueryOptionsSupplier getQueryOptionsSupplier() { - assertInitialised(); - return this.queryOptionsSupplier; - } - - public Locale getLocale(ServerPlayer player) { - if (this.language == null || !this.language.equals(player.getLanguage())) { - this.language = player.getLanguage(); - this.locale = TranslationManager.parseLocale(this.language); - } - - return this.locale; - } -} \ No newline at end of file diff --git a/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityListener.java b/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityListener.java deleted file mode 100644 index 3b4886ab0..000000000 --- a/forge/src/main/java/me/lucko/luckperms/forge/capabilities/UserCapabilityListener.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.lucko.luckperms.forge.capabilities; - -import me.lucko.luckperms.forge.LPForgePlugin; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.ICapabilityProvider; -import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.event.AttachCapabilitiesEvent; -import net.minecraftforge.event.entity.player.PlayerEvent; -import net.minecraftforge.eventbus.api.listener.SubscribeEvent; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class UserCapabilityListener { - - private final LPForgePlugin plugin; - - public UserCapabilityListener(LPForgePlugin plugin) { - this.plugin = plugin; - } - - @SubscribeEvent - public void onRegisterCapabilities(RegisterCapabilitiesEvent event) { - event.register(UserCapabilityImpl.class); - } - - @SubscribeEvent - public void onAttachCapabilities(AttachCapabilitiesEvent event) { - if (!(event.getObject() instanceof ServerPlayer)) { - return; - } - - event.addCapability(UserCapability.IDENTIFIER, new UserCapabilityProvider(new UserCapabilityImpl())); - } - - @SubscribeEvent - public void onPlayerClone(PlayerEvent.Clone event) { - Player previousPlayer = event.getOriginal(); - Player currentPlayer = event.getEntity(); - - if (!(currentPlayer instanceof ServerPlayer)) { - return; - } - - previousPlayer.reviveCaps(); - try { - UserCapabilityImpl previous = UserCapabilityImpl.get(previousPlayer); - UserCapabilityImpl current = UserCapabilityImpl.get(currentPlayer); - - current.initialise(previous, ((ServerPlayer) currentPlayer), this.plugin); - current.getQueryOptionsSupplier().invalidateCache(); - } catch (IllegalStateException e) { - // continue on if we cannot copy original data - } finally { - previousPlayer.invalidateCaps(); - } - } - - private static final class UserCapabilityProvider implements ICapabilityProvider { - private final UserCapabilityImpl userCapability; - - private UserCapabilityProvider(UserCapabilityImpl userCapability) { - this.userCapability = userCapability; - } - - @SuppressWarnings("unchecked") - @NotNull - @Override - public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { - if (cap != UserCapability.CAPABILITY) { - return LazyOptional.empty(); - } - - return LazyOptional.of(() -> (T) this.userCapability); - } - } - -} diff --git a/forge/src/main/java/me/lucko/luckperms/forge/context/ForgeContextManager.java b/forge/src/main/java/me/lucko/luckperms/forge/context/ForgeContextManager.java index bb0733df9..83280a1f9 100644 --- a/forge/src/main/java/me/lucko/luckperms/forge/context/ForgeContextManager.java +++ b/forge/src/main/java/me/lucko/luckperms/forge/context/ForgeContextManager.java @@ -25,19 +25,15 @@ package me.lucko.luckperms.forge.context; -import me.lucko.luckperms.common.context.manager.DetachedContextManager; -import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier; +import me.lucko.luckperms.common.context.manager.SimpleContextManager; import me.lucko.luckperms.forge.LPForgePlugin; -import me.lucko.luckperms.forge.capabilities.UserCapabilityImpl; import net.luckperms.api.query.OptionKey; import net.luckperms.api.query.QueryOptions; import net.minecraft.server.level.ServerPlayer; -import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Objects; import java.util.UUID; -public class ForgeContextManager extends DetachedContextManager { +public class ForgeContextManager extends SimpleContextManager { public static final OptionKey INTEGRATED_SERVER_OWNER = OptionKey.of("integrated_server_owner", Boolean.class); public ForgeContextManager(LPForgePlugin plugin) { @@ -49,16 +45,6 @@ public class ForgeContextManager extends DetachedContextManager T getPermission(ServerPlayer player, PermissionNode node, PermissionDynamicContext... context) { - UserCapabilityImpl capability = UserCapabilityImpl.getNullable(player); - - if (capability != null) { - User user = capability.getUser(); - QueryOptions queryOptions = capability.getQueryOptionsSupplier().getQueryOptions(); - + User user = this.plugin.getUserManager().getIfLoaded(player.getUUID()); + if (user != null) { + QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player); T value = getPermissionValue(user, queryOptions, node, context); if (value != null) { return value; diff --git a/forge/src/main/java/me/lucko/luckperms/forge/util/BrigadierInjector.java b/forge/src/main/java/me/lucko/luckperms/forge/util/BrigadierInjector.java index 365e6fbbe..3e3bee670 100644 --- a/forge/src/main/java/me/lucko/luckperms/forge/util/BrigadierInjector.java +++ b/forge/src/main/java/me/lucko/luckperms/forge/util/BrigadierInjector.java @@ -32,8 +32,7 @@ import me.lucko.luckperms.common.graph.Graph; import me.lucko.luckperms.common.graph.TraversalAlgorithm; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.forge.LPForgePlugin; -import me.lucko.luckperms.forge.capabilities.UserCapability; -import me.lucko.luckperms.forge.capabilities.UserCapabilityImpl; +import net.luckperms.api.query.QueryOptions; import net.luckperms.api.util.Tristate; import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.level.ServerPlayer; @@ -140,22 +139,15 @@ public final class BrigadierInjector { @Override public boolean test(CommandSourceStack source) { - if (source.getEntity() instanceof ServerPlayer) { - ServerPlayer player = (ServerPlayer) source.getEntity(); - Tristate state = Tristate.UNDEFINED; - // If player is still connecting and has not been added to world then check LP user directly - if (!player.isAddedToWorld()) { - User user = this.plugin.getUserManager().getIfLoaded(player.getUUID()); - if (user == null) { - // Should never happen but just in case... - return false; - } - state = user.getCachedData().getPermissionData().checkPermission(permission); - } else { - UserCapability user = UserCapabilityImpl.get(player); - state = user.checkPermission(this.permission); + if (source.getEntity() instanceof ServerPlayer player) { + User user = this.plugin.getUserManager().getIfLoaded(player.getUUID()); + if (user == null) { + return false; } + QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player); + Tristate state = user.getCachedData().getPermissionData(queryOptions).checkPermission(this.permission); + if (state != Tristate.UNDEFINED) { return state.asBoolean() && this.delegate.test(source.withPermission(4)); } diff --git a/settings.gradle b/settings.gradle index 71cc2068a..3cc8440c0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -32,7 +32,6 @@ include ( 'neoforge:loader', 'forge', 'forge:loader', - 'forge:forge-api', 'nukkit', 'nukkit:loader', 'sponge',