Skip to content
Merged
137 changes: 62 additions & 75 deletions src/main/java/ch/njol/skript/events/EvtClick.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
/**
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright Peter Güttinger, SkriptLang team and contributors
*/
package ch.njol.skript.events;

import ch.njol.util.Predicate;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Vehicle;
Expand Down Expand Up @@ -55,37 +39,37 @@ public class EvtClick extends SkriptEvent {
/**
* Tracks PlayerInteractEvents to deduplicate them.
*/
public static final ClickEventTracker interactTracker = new ClickEventTracker(Skript.getInstance());
public final static ClickEventTracker interactTracker = new ClickEventTracker(Skript.getInstance());

static {
Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array(
PlayerInteractEvent.class, PlayerInteractEntityEvent.class, PlayerInteractAtEntityEvent.class
);
Skript.registerEvent("Click", EvtClick.class, eventTypes,
"[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]",
"[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype%")
"[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype/blockdata%] [(with|using|holding) %-itemtype%]",
"[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype/blockdata%")
.description("Called when a user clicks on a block, an entity or air with or without an item in their hand.",
"Please note that rightclick events with an empty hand while not looking at a block are not sent to the server, so there's no way to detect them.",
"Also note that a leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event.")
.examples("on click:",
"on rightclick holding a fishing rod:",
"on leftclick on a stone or obsidian:",
"on rightclick on a creeper:",
"on click with a sword:")
.since("1.0");
"on click with a sword:",
"on click on chest[facing=north]:",
"on click on campfire[lit=true]:")
.since("1.0, INSERT VERSION (blockdata)");
}

/**
* Only trigger when one of these is interacted with.
*/
@Nullable
private Literal<?> type;
private @Nullable Literal<?> type;

/**
* Only trigger when then item player clicks with is one of these.
* Only trigger when the item that the player clicks with is one of these.
*/
@Nullable
private Literal<ItemType> tools;
private @Nullable Literal<ItemType> tools;

/**
* Click types to trigger.
Expand All @@ -97,18 +81,18 @@ public class EvtClick extends SkriptEvent {
public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) {
click = parseResult.mark == 0 ? ANY : parseResult.mark;
type = args[matchedPattern];
if (type != null && !ItemType.class.isAssignableFrom(type.getReturnType())) {
if (type != null && !ItemType.class.isAssignableFrom(type.getReturnType()) && !BlockData.class.isAssignableFrom(type.getReturnType())) {
Literal<EntityData<?>> entitydata = (Literal<EntityData<?>>) type;
if (click == LEFT) {
if (Vehicle.class.isAssignableFrom(entitydata.getSingle().getType())) {
Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event.");
Skript.error("A leftclick on a vehicle entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event.");
} else {
Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event.");
}
return false;
} else if (click == ANY) {
if (Vehicle.class.isAssignableFrom(entitydata.getSingle().getType())) {
Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event. " +
Skript.error("A leftclick on a vehicle entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event. " +
"Change this event to a rightclick to fix this warning message.");
} else {
Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event. " +
Expand All @@ -125,12 +109,11 @@ public boolean check(Event event) {
Block block;
Entity entity;

if (event instanceof PlayerInteractEntityEvent) {
PlayerInteractEntityEvent clickEvent = ((PlayerInteractEntityEvent) event);
Entity clicked = clickEvent.getRightClicked();
if (event instanceof PlayerInteractEntityEvent interactEntityEvent) {
Entity clicked = interactEntityEvent.getRightClicked();

// Usually, don't handle these events
if (clickEvent instanceof PlayerInteractAtEntityEvent) {
if (interactEntityEvent instanceof PlayerInteractAtEntityEvent) {
// But armor stands are an exception
// Later, there may be more exceptions...
if (!(clicked instanceof ArmorStand))
Expand All @@ -142,82 +125,86 @@ public boolean check(Event event) {

// PlayerInteractAtEntityEvent called only once for armor stands
if (!(event instanceof PlayerInteractAtEntityEvent)) {
if (!interactTracker.checkEvent(clickEvent.getPlayer(), clickEvent, clickEvent.getHand())) {
if (!interactTracker.checkEvent(interactEntityEvent.getPlayer(), interactEntityEvent, interactEntityEvent.getHand())) {
return false; // Not first event this tick
}
}

entity = clicked;
block = null;
} else if (event instanceof PlayerInteractEvent) {
PlayerInteractEvent clickEvent = ((PlayerInteractEvent) event);

} else if (event instanceof PlayerInteractEvent interactEvent) {
// Figure out click type, filter non-click events
Action a = clickEvent.getAction();
Action action = interactEvent.getAction();
int click;
switch (a) {
case LEFT_CLICK_AIR:
case LEFT_CLICK_BLOCK:
click = LEFT;
break;
case RIGHT_CLICK_AIR:
case RIGHT_CLICK_BLOCK:
click = RIGHT;
break;
case PHYSICAL: // Not a click event
default:
switch (action) {
case LEFT_CLICK_AIR, LEFT_CLICK_BLOCK -> click = LEFT;
case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> click = RIGHT;
default -> {
return false;
}
}
if ((this.click & click) == 0)
return false; // We don't want to handle this kind of events

EquipmentSlot hand = clickEvent.getHand();
EquipmentSlot hand = interactEvent.getHand();
assert hand != null; // Not PHYSICAL interaction
if (!interactTracker.checkEvent(clickEvent.getPlayer(), clickEvent, hand)) {
if (!interactTracker.checkEvent(interactEvent.getPlayer(), interactEvent, hand)) {
return false; // Not first event this tick
}

block = clickEvent.getClickedBlock();
block = interactEvent.getClickedBlock();
entity = null;
} else {
assert false;
return false;
}

if (tools != null && !tools.check(event, new Checker<ItemType>() {
@Override
public boolean check(final ItemType t) {
if (event instanceof PlayerInteractEvent) {
return t.isOfType(((PlayerInteractEvent) event).getItem());
} else { // PlayerInteractEntityEvent doesn't have item associated with it
PlayerInventory invi = ((PlayerInteractEntityEvent) event).getPlayer().getInventory();
ItemStack item = ((PlayerInteractEntityEvent) event).getHand() == EquipmentSlot.HAND
? invi.getItemInMainHand() : invi.getItemInOffHand();
return t.isOfType(item);
}

Checker<ItemType> checker = itemType -> {
if (event instanceof PlayerInteractEvent interactEvent) {
return itemType.isOfType(interactEvent.getItem());
} else {
PlayerInventory invi = ((PlayerInteractEntityEvent) event).getPlayer().getInventory();
ItemStack item = ((PlayerInteractEntityEvent) event).getHand() == EquipmentSlot.HAND
? invi.getItemInMainHand() : invi.getItemInOffHand();
return itemType.isOfType(item);
}
})) {
};

if (tools != null && !tools.check(event, checker))
return false;
}


if (type != null) {
BlockData blockDataCheck = block != null ? block.getBlockData() : null;
return type.check(event, new Checker<Object>() {
@Override
public boolean check(final Object o) {
public boolean check(Object object) {
if (entity != null) {
return o instanceof EntityData ? ((EntityData<?>) o).isInstance(entity) : Relation.EQUAL.isImpliedBy(DefaultComparators.entityItemComparator.compare(EntityData.fromEntity(entity), (ItemType) o));
} else {
return o instanceof EntityData ? false : ((ItemType) o).isOfType(block);
if (object instanceof EntityData<?> entityData) {
return entityData.isInstance(entity);
} else {
Relation compare = DefaultComparators.entityItemComparator.compare(EntityData.fromEntity(entity), (ItemType) object);
return Relation.EQUAL.isImpliedBy(compare);
}
} else if (object instanceof ItemType itemType) {
return itemType.isOfType(block);
} else if (blockDataCheck != null && object instanceof BlockData blockData) {
return blockDataCheck.matches(blockData);
}
return false;
}
});
}
return true;
}

@Override
public String toString(@Nullable Event e, boolean debug) {
return (click == LEFT ? "left" : click == RIGHT ? "right" : "") + "click" + (type != null ? " on " + type.toString(e, debug) : "") + (tools != null ? " holding " + tools.toString(e, debug) : "");
public String toString(@Nullable Event event, boolean debug) {
return switch (click) {
case LEFT -> "left";
case RIGHT -> "right";
default -> "";
} + "click" + (type != null ? " on " + type.toString(event, debug) : "") +
(tools != null ? " holding " + tools.toString(event, debug) : "");
}

}