/
SwitchCommand.java
211 lines (199 loc) · 9.73 KB
/
SwitchCommand.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package com.denizenscript.denizen.scripts.commands.world;
import com.denizenscript.denizen.Denizen;
import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.objects.MaterialTag;
import com.denizenscript.denizen.objects.properties.material.MaterialSwitchable;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import com.denizenscript.denizen.objects.LocationTag;
import com.denizenscript.denizencore.exceptions.InvalidArgumentsException;
import com.denizenscript.denizencore.objects.*;
import com.denizenscript.denizencore.objects.core.DurationTag;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.objects.core.ListTag;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Bell;
import org.bukkit.block.Block;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.TrapDoor;
import java.util.HashMap;
import java.util.Map;
public class SwitchCommand extends AbstractCommand {
public SwitchCommand() {
setName("switch");
setSyntax("switch [<location>|...] (state:{toggle}/on/off) (duration:<value>) (no_physics)");
setRequiredArguments(1, 4);
isProcedural = false;
}
// <--[command]
// @Name Switch
// @Syntax switch [<location>|...] (state:{toggle}/on/off) (duration:<value>) (no_physics)
// @Required 1
// @Maximum 4
// @Short Switches state of the block.
// @Synonyms Toggle,Lever,Activate,Power,Redstone
// @Group world
//
// @Description
// Changes the state of a block at the given location, or list of blocks at the given locations.
//
// Optionally specify "state:on" to turn a block on (or open it, or whatever as applicable) or "state:off" to turn it off (or close it, etc).
// By default, will toggle the state (on to off, or off to on).
//
// Optionally specify the "duration" argument to set a length of time after which the block will return to the original state.
//
// Works on any interactable blocks, including:
// - the standard toggling levers, doors, gates...
// - Single-use interactables like buttons, note blocks, dispensers, droppers, ...
// - Redstone interactables like repeaters, ...
// - Special interactables like tripwires, ...
// - Bells as a special case will ring when you use 'switch' on them, ...
// - Almost any other block with an interaction handler.
//
// This will generally (but not always) function equivalently to a user right-clicking the block
// (so it will open and close doors, flip levers on and off, press and depress buttons, ...).
//
// Optionally specify 'no_physics' to not apply a physics update after switching.
//
// @Tags
// <LocationTag.switched>
// <MaterialTag.switched>
//
// @Usage
// At the player's location, switch the state of the block to on, no matter what state it was in before.
// - switch <player.location> state:on
//
// @Usage
// Opens a door that the player is looking at.
// - switch <player.cursor_on> state:on
//
// @Usage
// Toggle a block at the player's foot location.
// - switch <player.location>
//
// -->
private enum SwitchState {ON, OFF, TOGGLE}
private Map<Location, Integer> taskMap = new HashMap<>(32);
@Override
public void addCustomTabCompletions(TabCompletionsBuilder tab) {
tab.addNotesOfType(LocationTag.class);
}
@Override
public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {
for (Argument arg : scriptEntry) {
if (!scriptEntry.hasObject("no_physics") &&
arg.matches("no_physics")) {
scriptEntry.addObject("no_physics", new ElementTag(true));
}
else if (!scriptEntry.hasObject("locations") &&
arg.matchesArgumentList(LocationTag.class)) {
scriptEntry.addObject("locations", arg.asType(ListTag.class));
}
else if (!scriptEntry.hasObject("duration") &&
arg.matchesArgumentType(DurationTag.class)) {
scriptEntry.addObject("duration", arg.asType(DurationTag.class));
}
else if (!scriptEntry.hasObject("state") &&
arg.matchesEnum(SwitchState.class)) {
scriptEntry.addObject("switchstate", new ElementTag(arg.getValue().toUpperCase()));
}
else {
arg.reportUnhandled();
}
}
if (!scriptEntry.hasObject("locations")) {
throw new InvalidArgumentsException("Must specify a location!");
}
scriptEntry.defaultObject("duration", new DurationTag(0));
scriptEntry.defaultObject("switchstate", new ElementTag("TOGGLE"));
}
@Override
public void execute(final ScriptEntry scriptEntry) {
final ListTag interactLocations = scriptEntry.getObjectTag("locations");
DurationTag duration = scriptEntry.getObjectTag("duration");
final SwitchState switchState = SwitchState.valueOf(scriptEntry.getElement("switchstate").asString());
ElementTag noPhysics = scriptEntry.getElement("no_physics");
// Switch the Block
if (scriptEntry.dbCallShouldDebug()) {
Debug.report(scriptEntry, getName(), interactLocations, duration, noPhysics, db("switchstate", switchState.name()));
}
final boolean physics = noPhysics == null || !noPhysics.asBoolean();
for (final LocationTag interactLocation : interactLocations.filter(LocationTag.class, scriptEntry)) {
switchBlock(scriptEntry, interactLocation, switchState, physics);
// If duration set, schedule a delayed task.
if (duration.getTicks() > 0) {
// If this block already had a delayed task, cancel it.
if (taskMap.containsKey(interactLocation)) {
try {
Bukkit.getScheduler().cancelTask(taskMap.get(interactLocation));
}
catch (Exception e) {
}
}
Debug.echoDebug(scriptEntry, "Setting delayed task 'SWITCH' for " + interactLocation.identify());
// Store new delayed task ID, for checking against, then schedule new delayed task.
taskMap.put(interactLocation, Bukkit.getScheduler().scheduleSyncDelayedTask(Denizen.getInstance(), () -> switchBlock(scriptEntry, interactLocation, SwitchState.TOGGLE, physics), duration.getTicks()));
}
}
}
public static boolean switchState(Block b) {
MaterialSwitchable switchable = MaterialSwitchable.getFrom(new MaterialTag(b.getBlockData()));
if (switchable == null) {
return false;
}
return switchable.getState();
}
// Break off this portion of the code from execute() so it can be used in both execute and the delayed runnable
public void switchBlock(ScriptEntry scriptEntry, Location interactLocation, SwitchState switchState, boolean physics) {
Block block = interactLocation.getBlock();
BlockData data1 = block.getBlockData();
MaterialTag materialTag = new MaterialTag(data1);
MaterialSwitchable switchable = MaterialSwitchable.getFrom(materialTag);
if (switchable == null) {
Debug.echoError("Cannot switch block of type '" + materialTag.getMaterial().name() + "'");
return;
}
if (materialTag.getMaterial() == Material.BELL) {
NMSHandler.blockHelper.ringBell((Bell) block.getState());
return;
}
boolean currentState = switchable.getState();
if ((switchState.equals(SwitchState.ON) && !currentState) || (switchState.equals(SwitchState.OFF) && currentState) || switchState.equals(SwitchState.TOGGLE)) {
switchable.setState(!currentState);
if (physics) {
block.setBlockData(switchable.material.getModernData());
}
else {
ModifyBlockCommand.setBlock(block.getLocation(), materialTag, false, null);
}
if (data1 instanceof Bisected && !(data1 instanceof TrapDoor)) { // TrapDoor implements Bisected, but is not actually bisected???
Location other = interactLocation.clone();
if (((Bisected) data1).getHalf() == Bisected.Half.TOP) {
other = other.add(0, -1, 0);
}
else {
other = other.add(0, 1, 0);
}
BlockData data2 = other.getBlock().getBlockData();
if (data2.getMaterial() == data1.getMaterial()) {
MaterialSwitchable switchable2 = MaterialSwitchable.getFrom(new MaterialTag(data2));
switchable2.setState(!currentState);
other.getBlock().setBlockData(switchable2.material.getModernData());
if (physics) {
AdjustBlockCommand.applyPhysicsAt(other);
}
}
}
if (physics) {
AdjustBlockCommand.applyPhysicsAt(interactLocation);
}
if (scriptEntry.dbCallShouldDebug()) {
Debug.echoDebug(scriptEntry, "Switched " + block.getType() + "! Current state now: " + (switchState(block) ? "ON" : "OFF"));
}
}
}
}