Skip to content

Commit

Permalink
Glow command rewrite (#2466)
Browse files Browse the repository at this point in the history
* WIP rewrite, add helper for `glow` and `invisible`

* `perPlayerMap` -> `packetOverrides`

* Prefix isn't required for enum args (`state:`)

* Update meta

* Remove `state:` tab-complete

* `applyByteFlag` -> `applyEntityDataFlag`, imports

* Slight cleanup/opti

* Optimize `setForPlayers` modification checking

* Minor code cleanup

* Use `Predicate` instead of `Function<X, Boolean>`

* Fix `isGlowing` (only need the bukkit method)

* Move `EntityMetadataCommandHelper` to `utilities`

* Move `GlowCommand` to `entity` package + group

* Lowercase `state` argument default value

* Fix `invisible` command syntax meta
  • Loading branch information
tal5 committed Apr 24, 2023
1 parent fc19095 commit 8976f3f
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 451 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.denizenscript.denizen.scripts.commands.entity;

import com.denizenscript.denizen.objects.EntityTag;
import com.denizenscript.denizen.objects.PlayerTag;
import com.denizenscript.denizen.utilities.entity.EntityMetadataCommandHelper;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;
import com.denizenscript.denizencore.scripts.commands.generator.*;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;

import java.util.List;

public class GlowCommand extends AbstractCommand {

public static final EntityMetadataCommandHelper helper = new EntityMetadataCommandHelper(Entity::isGlowing, GlowCommand::setGlowing);

public GlowCommand() {
setName("glow");
setSyntax("glow (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)");
setRequiredArguments(0, 3);
isProcedural = false;
autoCompile();
}

// <--[command]
// @Name Glow
// @Syntax glow (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)
// @Required 0
// @Maximum 3
// @Short Sets whether an NPC or entity is glowing.
// @Group entity
//
// @Description
// Sets whether the specified entities glow, defaults to the linked player/NPC if none were specified.
//
// Optionally specify 'for:' with a list of players to fake the entities' glow state for these players.
// When using 'toggle' with the 'for:' argument, the glow state will be toggled for each player separately.
// If unspecified, will be set globally.
// 'for:' players remain tracked even when offline/reconnecting, but are forgotten after server restart.
//
// When not using 'for:', the glow is global / on the real entity, which will persist in that entity's data until changed.
//
// To reset an entity's fake glow use the 'reset' state.
// A reset is global by default, use the 'for:' argument to reset specific players.
//
// @Tags
// <EntityTag.glowing>
//
// @Usage
// Use to make the linked player (or NPC, if there isn't one) glow.
// - glow
//
// @Usage
// Use to toggle whether the linked NPC is glowing.
// - glow <npc> toggle
//
// @Usage
// Use to make an entity glow for specific players, without changing the way other players see it.
// - glow <[entity]> for:<[player1]>|<[player2]>
//
// @Usage
// Use to reset an entity's fake glow state for the linked player.
// - glow <[entity]> reset for:<player>
// -->

public static void setGlowing(EntityTag entity, boolean glowing) {
if (entity.isCitizensNPC()) {
entity.getDenizenNPC().getCitizen().data().setPersistent(NPC.Metadata.GLOWING, glowing);
}
else {
entity.getBukkitEntity().setGlowing(glowing);
}
}

public static void autoExecute(ScriptEntry scriptEntry,
@ArgName("target") @ArgLinear @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> targets,
@ArgName("state") @ArgDefaultText("true") EntityMetadataCommandHelper.Action action,
@ArgName("for") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers) {
helper.execute(scriptEntry, targets, action, forPlayers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,49 @@
import com.denizenscript.denizen.objects.EntityTag;
import com.denizenscript.denizen.objects.NPCTag;
import com.denizenscript.denizen.objects.PlayerTag;
import com.denizenscript.denizen.utilities.Utilities;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import com.denizenscript.denizen.utilities.packets.NetworkInterceptHelper;
import com.denizenscript.denizencore.exceptions.InvalidArgumentsException;
import com.denizenscript.denizencore.objects.Argument;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizen.utilities.entity.EntityMetadataCommandHelper;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;
import org.bukkit.entity.*;
import com.denizenscript.denizencore.scripts.commands.generator.*;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.LivingEntity;
import org.bukkit.potion.PotionEffectType;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;

public class InvisibleCommand extends AbstractCommand {

public static final EntityMetadataCommandHelper helper = new EntityMetadataCommandHelper(InvisibleCommand::isInvisible, InvisibleCommand::setInvisible);

public InvisibleCommand() {
setName("invisible");
setSyntax("invisible (<entity>) (state:true/false/toggle/reset) (for:<player>|...)");
setPrefixesHandled("for");
setSyntax("invisible (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)");
setRequiredArguments(0, 3);
isProcedural = false;
autoCompile();
}

// <--[command]
// @Name Invisible
// @Syntax invisible (<entity>) (state:true/false/toggle/reset) (for:<player>|...)
// @Syntax invisible (<entity>|...) ({true}/false/toggle/reset) (for:<player>|...)
// @Required 0
// @Maximum 3
// @Short Sets whether an NPC or entity are invisible.
// @Group entity
//
// @Description
// Makes the specified entity invisible (equivalent to an invisibility potion), defaults to the linked player/NPC if one wasn't specified.
// Sets whether the specified entities are invisible (equivalent to an invisibility potion), defaults to the linked player/NPC if none were specified.
// If an NPC was specified, the 'invisible' trait is applied.
//
// Optionally specify 'for:' with a list of players to fake the entity's visibility state for these players.
// When using the 'toggle' state with the 'for:' argument, the visibility state will be toggled for each player separately.
// Optionally specify 'for:' with a list of players to fake the entities' visibility state for these players.
// When using 'toggle' with the 'for:' argument, the visibility state will be toggled for each player separately.
// If unspecified, will be set globally.
// 'For' players remain tracked even when offline/reconnecting, but are forgotten after server restart.
// 'for:' players remain tracked even when offline/reconnecting, but are forgotten after server restart.
// Note that using the 'for:' argument won't apply the 'invisible' trait to NPCs.
//
// When not using 'for', the effect is global / on the real entity, which will persist in that entity's data until changed.
// When not using 'for:', the effect is global / on the real entity, which will persist in that entity's data until changed.
//
// To reset an entity's fake visibility use the 'reset' state.
// A reset is global by default, use the 'for:' argument to reset specific players.
Expand All @@ -66,185 +64,62 @@ public InvisibleCommand() {
//
// @Usage
// Use to make the linked NPC visible if previously invisible, and invisible if not.
// - invisible <npc> state:toggle
// - invisible <npc> toggle
//
// @Usage
// Use to make an entity visible for specific players, without changing the way other players see it.
// - invisible <[entity]> state:false for:<[player1]>|<[player2]>
// - invisible <[entity]> false for:<[player1]>|<[player2]>
//
// @Usage
// Use to reset an entity's fake visibility state for the linked player.
// - invisible <[entity]> state:reset for:<player>
// - invisible <[entity]> reset for:<player>
// -->

@Override
public void addCustomTabCompletions(TabCompletionsBuilder tab) {
tab.addWithPrefix("state:", Action.values());
}

enum Action {TRUE, FALSE, TOGGLE, RESET}

@Override
public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {
for (Argument arg : scriptEntry) {
if (!scriptEntry.hasObject("state")
&& arg.matchesEnum(Action.class)) {
scriptEntry.addObject("state", arg.asElement());
}
else if (!scriptEntry.hasObject("target")
&& arg.matches("player")
&& Utilities.entryHasPlayer(scriptEntry)) {
scriptEntry.addObject("target", Utilities.getEntryPlayer(scriptEntry).getDenizenEntity());
}
else if (!scriptEntry.hasObject("target")
&& arg.matches("npc")
&& Utilities.entryHasNPC(scriptEntry)) {
scriptEntry.addObject("target", Utilities.getEntryNPC(scriptEntry).getDenizenEntity());
}
else if (!scriptEntry.hasObject("target")
&& arg.matchesArgumentType(EntityTag.class)) {
scriptEntry.addObject("target", arg.asType(EntityTag.class));
}
else {
arg.reportUnhandled();
}
public static boolean isInvisible(Entity entity) {
if (EntityTag.isCitizensNPC(entity)) {
InvisibleTrait invisibleTrait = NPCTag.fromEntity(entity).getCitizen().getTraitNullable(InvisibleTrait.class);
return invisibleTrait != null && invisibleTrait.isInvisible();
}
scriptEntry.defaultObject("state", new ElementTag("true"));
scriptEntry.defaultObject("target", Utilities.entryDefaultEntity(scriptEntry, true));
}

public static HashMap<UUID, HashMap<UUID, Boolean>> invisibleEntities = new HashMap<>();

public static void setInvisibleForPlayer(EntityTag target, PlayerTag player, boolean invisible) {
if (target == null || target.getUUID() == null || player == null) {
return;
else if (entity instanceof ArmorStand armorStand) {
return !armorStand.isVisible();
}
NetworkInterceptHelper.enable();
boolean wasModified = !invisibleEntities.containsKey(target.getUUID());
HashMap<UUID, Boolean> playerMap = invisibleEntities.computeIfAbsent(target.getUUID(), k -> new HashMap<>());
wasModified = !playerMap.containsKey(player.getUUID()) || playerMap.get(player.getUUID()) != invisible || wasModified;
playerMap.put(player.getUUID(), invisible);
if (wasModified && player.isOnline()) {
NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), target.getBukkitEntity());
else if (entity instanceof ItemFrame itemFrame) {
return !itemFrame.isVisible();
}
else if (entity instanceof LivingEntity livingEntity) {
return livingEntity.isInvisible() || livingEntity.hasPotionEffect(PotionEffectType.INVISIBILITY);
}
else {
return NMSHandler.entityHelper.isInvisible(entity);
}
}

public void setInvisible(EntityTag entity, boolean invisible) {
public static void setInvisible(EntityTag entity, boolean invisible) {
if (entity.isCitizensNPC()) {
entity.getDenizenNPC().getCitizen().getOrAddTrait(InvisibleTrait.class).setInvisible(invisible);
}
else if (entity.getBukkitEntity() instanceof ArmorStand) {
((ArmorStand) entity.getBukkitEntity()).setVisible(!invisible);
else if (entity.getBukkitEntity() instanceof ArmorStand armorStand) {
armorStand.setVisible(!invisible);
}
else if (entity.getBukkitEntity() instanceof ItemFrame) {
((ItemFrame) entity.getBukkitEntity()).setVisible(!invisible);
else if (entity.getBukkitEntity() instanceof ItemFrame itemFrame) {
itemFrame.setVisible(!invisible);
}
else if (entity.isLivingEntity() && !entity.isFake) {
entity.getLivingEntity().setInvisible(invisible);
else if (!entity.isFake && entity.getBukkitEntity() instanceof LivingEntity livingEntity) {
livingEntity.setInvisible(invisible);
if (!invisible) {
// Remove the invisibility potion effect for compact with old uses (the command used to add it)
entity.getLivingEntity().removePotionEffect(PotionEffectType.INVISIBILITY);
livingEntity.removePotionEffect(PotionEffectType.INVISIBILITY);
}
}
else {
NMSHandler.entityHelper.setInvisible(entity.getBukkitEntity(), invisible);
}
}

@Override
public void execute(ScriptEntry scriptEntry) {
EntityTag target = scriptEntry.getObjectTag("target");
if (target == null) {
Debug.echoError(scriptEntry, "Must specify a valid target.");
return;
}
ElementTag state = scriptEntry.getElement("state");
List<PlayerTag> forPlayers = scriptEntry.argForPrefixList("for", PlayerTag.class, true);
if (scriptEntry.dbCallShouldDebug()) {
Debug.report(scriptEntry, getName(), target, state, db("for", forPlayers));
}
switch (state.asEnum(Action.class)) {
case TRUE:
case FALSE:
if (forPlayers == null) {
setInvisible(target, state.asBoolean());
}
else {
boolean invisible = state.asBoolean();
for (PlayerTag player : forPlayers) {
setInvisibleForPlayer(target, player, invisible);
}
}
break;
case TOGGLE:
if (forPlayers == null) {
setInvisible(target, !isInvisible(target.getBukkitEntity(), null, false));
}
else {
for (PlayerTag player : forPlayers) {
setInvisibleForPlayer(target, player, !isInvisible(target.getBukkitEntity(), player.getUUID(), false));
}
}
break;
case RESET:
HashMap<UUID, Boolean> playerMap = invisibleEntities.get(target.getUUID());
if (playerMap == null) {
return;
}
HashSet<UUID> playersToUpdate = new HashSet<>();
if (forPlayers == null) {
playersToUpdate.addAll(playerMap.keySet());
invisibleEntities.remove(target.getUUID());
}
else {
for (PlayerTag player : forPlayers) {
playerMap.remove(player.getUUID());
playersToUpdate.add(player.getUUID());
}
if (playerMap.isEmpty()) {
invisibleEntities.remove(target.getUUID());
}
}
if (!playersToUpdate.isEmpty()) {
for (Player player : NMSHandler.entityHelper.getPlayersThatSee(target.getBukkitEntity())) {
if (playersToUpdate.contains(player.getUniqueId())) {
NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player, target.getBukkitEntity());
}
}
}
break;
}
public static void autoExecute(ScriptEntry scriptEntry,
@ArgName("target") @ArgLinear @ArgDefaultNull @ArgSubType(EntityTag.class) List<EntityTag> targets,
@ArgName("state") @ArgDefaultText("true") EntityMetadataCommandHelper.Action action,
@ArgName("for") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers) {
helper.execute(scriptEntry, targets, action, forPlayers);
}

public static Boolean isInvisible(Entity entity, UUID player, boolean fakeOnly) {
if (entity == null) {
return null;
}
if (player != null) {
HashMap<UUID, Boolean> playerMap = invisibleEntities.get(entity.getUniqueId());
if (playerMap != null && playerMap.containsKey(player)) {
return playerMap.get(player);
}
}
if (fakeOnly) {
return null;
}
if (EntityTag.isCitizensNPC(entity)) {
InvisibleTrait invisibleTrait = NPCTag.fromEntity(entity).getCitizen().getTraitNullable(InvisibleTrait.class);
return invisibleTrait != null && invisibleTrait.isInvisible();
}
else if (entity instanceof ArmorStand) {
return !((ArmorStand) entity).isVisible();
}
else if (entity instanceof ItemFrame) {
return !((ItemFrame) entity).isVisible();
}
else if (entity instanceof LivingEntity) {
return ((LivingEntity) entity).isInvisible() || ((LivingEntity) entity).hasPotionEffect(PotionEffectType.INVISIBILITY);
}
else {
return NMSHandler.entityHelper.isInvisible(entity);
}
}

}
Loading

0 comments on commit 8976f3f

Please sign in to comment.