-
-
Notifications
You must be signed in to change notification settings - Fork 30
/
FlagCommand.java
227 lines (216 loc) · 11.5 KB
/
FlagCommand.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package com.denizenscript.denizencore.scripts.commands.core;
import com.denizenscript.denizencore.DenizenCore;
import com.denizenscript.denizencore.exceptions.InvalidArgumentsException;
import com.denizenscript.denizencore.flags.AbstractFlagTracker;
import com.denizenscript.denizencore.flags.FlaggableObject;
import com.denizenscript.denizencore.objects.Argument;
import com.denizenscript.denizencore.objects.ObjectTag;
import com.denizenscript.denizencore.objects.core.DurationTag;
import com.denizenscript.denizencore.objects.core.ListTag;
import com.denizenscript.denizencore.objects.core.TimeTag;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;
import com.denizenscript.denizencore.utilities.CoreUtilities;
import com.denizenscript.denizencore.utilities.data.ActionableDataProvider;
import com.denizenscript.denizencore.utilities.data.DataAction;
import com.denizenscript.denizencore.utilities.data.DataActionHelper;
import com.denizenscript.denizencore.utilities.debugging.Debug;
public class FlagCommand extends AbstractCommand {
public FlagCommand() {
setName("flag");
setSyntax("flag [<object>|...] [<name>([<#>])](:<action>)[:<value>] (duration:<value>)");
setRequiredArguments(1, 3);
isProcedural = false;
}
// <--[language]
// @name Flag System
// @group Denizen Scripting Language
// @description
// The flag system is a core feature of Denizen, that allows for persistent data storage linked to objects.
//
// "Persistent" means the data is still around even after a server restart or anything else, and is only removed when you choose for it to be removed.
// "Linked to objects" means rather than purely global values, flags are associated with a player, or an NPC, or a block, or whatever else.
//
// See also the guide page at <@link url https://guide.denizenscript.com/guides/basics/flags.html>.
//
// For non-persistent temporary memory, see instead <@link command define>.
// For more generic memory options, see <@link command yaml> or <@link command sql>.
//
// Flags can be sub-mapped with the '.' key, meaning a flag named 'x.y.z' is actually a flag 'x' as a MapTag with key 'y' as a MapTag with key 'z' as the final flag value.
// In other words, "<server.flag[a.b.c]>" is equivalent to "<server.flag[a].get[b].get[c]>"
//
// The currently supported flag targets are:
// - 'server', essentially a global flag target, that will store data in the file "plugins/Denizen/server_flags.dat"
// - PlayerTag, which will store data in the file "plugins/Denizen/player_flags/[UUID_HERE].dat"
// - NPCTag (Citizens), which will store data in the Citizens saves file as the 'denizen_flags' trait.
// - EntityTag, which will store data in the entity's NBT in the world file.
// - LocationTag, for block flags, which will store data in the chunk file.
// - ItemTag, which will store data in the item's NBT.
// - WorldTag, which will store in the world folder in a file named 'denizen_flags.dat', like "server/world/denizen_flags.dat"
//
// Most flag sets are handled by <@link command flag>, however items are primarily flagged via <@link command inventory> with the 'flag' argument.
// Any supported object type, including the 'server' base tag, can use the tags
// <@link tag FlaggableObject.flag>, <@link tag FlaggableObject.has_flag>, <@link tag FlaggableObject.flag_expiration>, <@link tag FlaggableObject.list_flags>.
//
// Additionally, flags be searched for with tags like <@link tag server.online_players_flagged>, <@link tag server.players_flagged>, <@link tag server.spawned_npcs_flagged>,
// <@link tag server.npcs_flagged>, <@link tag InventoryTag.contains.flagged>, <@link tag InventoryTag.quantity.flagged>, ...
// Flags can also be required by script event lines, as explained at <@link language Script Event Switches>.
// Item flags can also be used as a requirement in <@link command take>.
//
// Note that some internal flags exist, and are prefixed with '__' to avoid conflict with normal user flags.
// This includes '__interact_step' which is used for interact script steps, related to <@link command zap>,
// and '__interact_cooldown' which is used for interact script cooldowns, related to <@link command cooldown>.
//
// Flags have an expiration system, which is used by specifying a duration after which they should expire (which then internally calculates the date/time of expiration by adding the duration input to the current date/time).
// Expirations are then *checked for* in flag tags - meaning, the flag tags will internally compare a stored date/time against the real current date/time,
// and if the flag's expiration time is in the past, the flag tag will return values equivalent to if the flag doesn't exist.
// There is no system actively monitoring for flag expirations or applying them. There is no event for expirations occurring, as they don't "occur" per se.
// In other words, it is correct to say a flag "is expired" or a flag "is not expired",
// but it is incorrect to say a flag "expires", as it is not an active action (those this wording can be convenient when speaking informally).
// Expired flags are sometimes 'cleaned up' (meaning, any expired flags get actually removed from internal storage), usually when a flag save file is loaded into the server.
// -->
// <--[command]
// @Name Flag
// @Syntax flag [<object>|...] [<name>([<#>])](:<action>)[:<value>] (duration:<value>)
// @Required 2
// @Maximum 3
// @Short Sets or modifies a flag on any flaggable object.
// @Group core
// @Guide https://guide.denizenscript.com/guides/basics/flags.html
//
// @Description
// The flag command sets or modifies custom data values stored on any flaggable object (the server, a player/NPC/entity, a block location, ...).
// See also <@link language flag system>.
//
// This command supports data actions, see <@link language data actions>.
//
// @Tags
// <FlaggableObject.flag[<flag_name>]>
// <FlaggableObject.has_flag[<flag_name>]>
// <FlaggableObject.flag_expiration[<flag_name>]>
// <FlaggableObject.list_flags>
// <server.online_players_flagged[<flag_name>]>
// <server.players_flagged[<flag_name>]>
// <server.spawned_npcs_flagged[<flag_name>]>
// <server.npcs_flagged[<flag_name>]>
//
// @Usage
// Use to create or set a flag on a player.
// - flag <player> playstyle:aggressive
//
// @Usage
// Use to flag an npc with a given tag value.
// - flag <npc> location:<npc.location>
//
// @Usage
// Use to apply mathematical changes to a flag's value on a unique object.
// - flag <context.damager> damage_dealt:+:<context.damage>
//
// @Usage
// Use to add an item to a server flag as a new value without removing existing values.
// - flag server cool_people:->:<[player]>
//
// @Usage
// Use to add multiple items as individual new values to a server flag that is already a list.
// - flag server cool_people:|:<[player]>|<[someplayer]>
//
// @Usage
// Use to remove an entry from a server flag.
// - flag server cool_people:<-:<[someplayer]>
//
// @Usage
// Use to clear a flag and fill it with a new list of values.
// - flag server cool_people:!|:<[player]>|<[someplayer]>|<[aplayer]>
//
// @Usage
// Use to completely remove a flag.
// - flag server cool_people:!
//
// @Usage
// Use to modify a specific index in a list flag.
// - flag server myflag[3]:HelloWorld
// -->
@Override
public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {
for (Argument arg : scriptEntry.getProcessedArgs()) {
if (!scriptEntry.hasObject("targets")
&& !arg.hasPrefix()) {
scriptEntry.addObject("targets", arg.asType(ListTag.class));
}
else if (!scriptEntry.hasObject("duration")
&& arg.matchesPrefix("duration")
&& arg.matchesArgumentType(DurationTag.class)) {
scriptEntry.addObject("duration", arg.asType(DurationTag.class));
}
else if (!scriptEntry.hasObject("flag_action")) {
scriptEntry.addObject("flag_action", DataActionHelper.parse(new FlagActionProvider(), arg, scriptEntry.context));
}
else {
arg.reportUnhandled();
}
}
if (!scriptEntry.hasObject("targets")) {
throw new InvalidArgumentsException("Must specify flag target(s)!");
}
if (!scriptEntry.hasObject("flag_action")) {
throw new InvalidArgumentsException("Must specify a flag to set!");
}
}
public static class FlagActionProvider extends ActionableDataProvider {
public AbstractFlagTracker tracker;
public TimeTag expiration;
@Override
public ObjectTag getValueAt(String keyName) {
return tracker.getFlagValue(keyName);
}
@Override
public void setValueAt(String keyName, ObjectTag value) {
if (keyName.startsWith("__")) {
Debug.echoError("Assigning flag value to reserved '__' namespace (key: '" + keyName + "')");
}
tracker.setFlag(keyName, value, expiration);
}
}
@Override
public void execute(ScriptEntry scriptEntry) {
ListTag targets = scriptEntry.getObjectTag("targets");
DurationTag duration = scriptEntry.getObjectTag("duration");
DataAction flagAction = (DataAction) scriptEntry.getObject("flag_action");
if (scriptEntry.dbCallShouldDebug()) {
Debug.report(scriptEntry, getName(), targets.debug()
+ (duration == null ? "" : duration.debug())
+ flagAction.debug());
}
if (duration != null) {
((FlagActionProvider) flagAction.provider).expiration = new TimeTag(TimeTag.now().millis() + duration.getMillis());
}
for (ObjectTag object : targets.objectForms) {
AbstractFlagTracker tracker;
if (CoreUtilities.equalsIgnoreCase(object.toString(), "server")) {
tracker = DenizenCore.getImplementation().getServerFlags();
}
else if (object instanceof FlaggableObject) {
tracker = ((FlaggableObject) object).getFlagTracker();
}
else {
FlaggableObject obj = DenizenCore.getImplementation().simpleWordToFlaggable(object.toString(), scriptEntry);
if (obj != null) {
tracker = obj.getFlagTracker();
}
else {
Debug.echoError("Cannot flag '" + object + "': that object type is not flaggable!");
continue;
}
}
if (tracker == null) {
Debug.echoError("Something went wrong, cannot flag '" + object + "'...");
continue;
}
((FlagActionProvider) flagAction.provider).tracker = tracker;
flagAction.execute(scriptEntry.getContext());
if (object instanceof FlaggableObject) {
((FlaggableObject) object).reapplyTracker(tracker);
}
}
}
}