Selecting edits
A Selection chooses edits. Lookups read the edits it chooses, rollbacks and restores act on them, and purges delete them. It is the API’s version of the filters staff type after /lookup and /rollback.
Start from Selection.all(), which chooses every edit, and narrow it:
Selection grief = Selection.all() .by(griefer) .within(Duration.ofHours(2)) .near(staff.getLocation(), 30) .kinds(Kind.BLOCK, Kind.CONTAINER) .not().causes(Cause.WORLDEDIT);A selection is split into parts: who, when, where, the kinds, the causes, the materials, and what it leaves out. Each method sets one part and replaces what that part held before. A part you never set does not narrow the selection, so Selection.all() holds every edit of every kind, chat, commands and logins included. Every method returns a new selection and leaves the old one as it was.
| Method | Chooses |
|---|---|
by(OfflinePlayer... players) |
Edits by these players, whether the player acted or started a chain the world carried on. |
by(UUID... ids) |
Edits by these people: player ids, or Actor.id() ids. |
by(Actor... actors) |
Edits these actors made or carried, such as Actor.LAVA: the lava with nobody behind it, and the lava that flowed from any player’s bucket. |
People and actors share one part. An edit matches when its person, or the actor that carried it, is one of the ids you name. So:
by(player)chooses what the player did, and what the world carried on for them: the lava they poured and the fire that lava started.by(Actor.LAVA)chooses all lava, whoever is behind it. It is what staff get withuser #lavain a command.
The three by methods replace one another, so by(player).by(Actor.LAVA) selects all lava, not the player’s lava. To mix players and actors in one selection, pass their ids to by(UUID...). An actor’s id there works as it does in by(Actor...):
Selection griefOrCreepers = Selection.all().by(griefer.getUniqueId(), Actor.CREEPER.id());To find only the lava one player poured, select the player and check edit.actor() on each edit.
A lookup of by(player) also reads the player’s logins and logouts. Add kinds(Kind.BLOCK) to read blocks alone.
| Method | Chooses |
|---|---|
within(Duration duration) |
Edits in the last duration, counted from the moment the selection is read. |
since(Instant time) |
Edits at or after this time. |
until(Instant time) |
Edits at or before this time. |
Times are inclusive at both ends. The three methods share the time part, and each one replaces what it finds there:
withinreplaces the whole time part. It removes asinceand anuntil.sincereplaces awithinwindow, and keeps anuntil.untilreplaces awithinwindow, and keeps asince.
A window always runs up to the moment of the read, so a selection never holds a window and an until. For a span that ends in the past, use since and until with an Instant:
// Edits from the last day, up to one hour ago.Instant now = Instant.now();Selection dayBefore = Selection.all() .since(now.minus(Duration.ofDays(1))) .until(now.minus(Duration.ofHours(1)));
// Edits older than thirty days.Selection old = Selection.all().until(Instant.now().minus(Duration.ofDays(30)));since after until throws IllegalArgumentException.
A window counts back from when it runs
Section titled “A window counts back from when it runs”within keeps the duration, not a fixed time. A lookup, an estimate or a start counts it back from the moment it reads the selection. So a request you build at noon and start at one o’clock, with within(Duration.ofHours(2)), rolls back the edits from eleven to one.
// Built once, in onEnable.OperationRequest lastTwoHours = oasis.operations() .rollback(Selection.all().by(griefer).within(Duration.ofHours(2)));
// Started later. Each start reads the two hours before it.lastTwoHours.start();A start fixes the window once, when it begins. Its sweeps and its forceKept() request then read the same edits as its first pass.
since and until take an Instant, so they never move.
| Method | Chooses |
|---|---|
in(World... worlds) |
Edits anywhere in these worlds. |
in(String... worlds) |
Edits anywhere in these worlds by their stored names, for a world that is not loaded. |
near(Location center, int radius) |
Edits in the cube of this radius, in blocks, around the location. |
box(Location corner, Location opposite) |
Edits in the box between two corners of one world. |
at(Block block) |
Edits at one block, of every kind. |
in(String world, Selection.Box box) |
Edits in a box of a world by its stored name, for a world that is not loaded. The box is never null. |
All six share one part, the world and the box, so the last one you call wins. Boxes are inclusive. near is a cube, not a sphere, so near(location, 0) is the one block at that location. box takes its corners in any order.
Selection.Box holds minX, minY, minZ, maxX, maxY and maxZ, all inclusive. It throws IllegalArgumentException for a lowest bound above its highest:
// The spawn area of a world that is not loaded right now.Selection spawn = Selection.all().in("world_old", new Selection.Box(-64, -64, -64, 64, 320, 64));A world that is not loaded
Section titled “A world that is not loaded”in(String... worlds) and in(String world, Box box) name a world that no server world has now. A world is not loaded when a staff member unloaded it, or when its folder is gone. The stored name is the one the world had when Oasis logged its edits.
A lookup and a purge read and write the store alone. They take any world that is loaded, that Oasis saw loaded, or that holds edits in the store, with no folder needed. A purge of whole worlds deletes the history of such a world.
A rollback, a restore and an undo write the world, so they need it. Oasis loads a world that is not loaded for the pass: through the world loader of the plugin that keeps it, or from its folder. When nobody can load it, the operation fails with WORLD_GONE before it writes anything.
Names are exact, as World.getName() reads them. Oasis checks every world name of a selection, chosen or left out, when a request reads it: a lookup, a history, an estimate, an operation, or a purge with its count. A name that is not loaded under that exact name, that Oasis never saw, and that the store holds no edits of fails the request with Failure.Reason.NO_SUCH_WORLD, so a typo never widens a rollback.
| Method | Chooses |
|---|---|
kinds(Kind... values) |
Edits of these kinds only, such as Kind.BLOCK. |
causes(Cause... values) |
Edits with these causes only, such as Cause.BREAKING. |
materials(Material... values) |
Edits of these blocks or items, before or after the edit. |
entities(EntityType... values) |
Edits of these entities. |
matching(String... patterns) |
Edits of blocks, items or entities by id or by a pattern of ids. |
kinds and causes each have a part of their own. materials, entities and matching share one part, so the last of them you call wins. To choose blocks and entities in one selection, use matching with both ids:
Selection signsAndFrames = Selection.all().matching("*_sign", "item_frame");Vocabulary lists every Kind and Cause.
Patterns
Section titled “Patterns”matching and not().matching take ids and patterns of ids:
- An id is a namespaced id, such as
minecraft:oak_logorminecraft:zombie. - An id with no namespace is a vanilla one:
oak_logmeansminecraft:oak_log. - A
*matches any run of characters:*_signmatches every sign, and*_oreevery ore. - Oasis reads a pattern in lower case, so
OAK_LOGworks too.
A value may hold only letters, digits, _, -, ., / and *, with at most one : between the namespace and the path. Anything else, such as a space or a #, throws IllegalArgumentException.
Leaving edits out
Section titled “Leaving edits out”not() returns the parts a selection leaves out. Each method takes the same values as the method of the same name above, leaves them out, and returns the selection.
| Method | Leaves out |
|---|---|
not().by(OfflinePlayer...), not().by(UUID...), not().by(Actor...) |
The edits of these people or actors, and every chain they started or carried. |
not().in(World...), not().in(String...) |
Edits in these worlds. |
not().kinds(Kind...) |
Edits of these kinds. |
not().causes(Cause...) |
Edits with these causes. |
not().materials(Material...) |
Edits of these blocks or items. |
not().entities(EntityType...) |
Edits of these entities. |
not().matching(String...) |
Edits whose id matches one of these patterns. |
not().by(x) is the exact inverse of by(x): it leaves out what x did and the fire and water x set going.
Each call replaces its part, as the rest of the selection does. not().by(steve).not().by(alex) leaves out Alex only. not().materials, not().entities and not().matching share one part.
// Everything a player broke or placed, but not their signs.Selection handWork = Selection.all() .by(builder) .causes(Cause.BREAKING, Cause.PLACING) .not().matching("*_sign");
// Everything near a location, except what one builder did.Selection others = Selection.all().near(center, 20).not().by(builder);An empty list throws
Section titled “An empty list throws”Every method that takes a list of values throws IllegalArgumentException when the list is empty. That covers every by, in, kinds, causes, materials, entities and matching, and every one of them after not().
This protects you from a costly mistake. A part you never set chooses everything, so if an empty list meant “no filter”, it would choose every edit on the server. Picture a plugin that collects the griefers in a region, finds none, and rolls back by() of that empty list. It would roll back the whole server.
So check your list before you pass it:
public void rollBackGriefers(List<UUID> griefers, Location center){ if (griefers.isEmpty()) { return; } oasis.operations() .rollback(Selection.all().by(griefers.toArray(UUID[]::new)).near(center, 50)) .start();}To choose every player, leave the part unset.
A selection that matches nothing
Section titled “A selection that matches nothing”A selection that cannot match any edit by how it is built throws IllegalArgumentException at the call that makes it, in either order of the calls:
- A person, world, kind, cause or material is both chosen and left out.
near,box,atorin(name, box)names a world that is left out.sinceis afteruntil.
Every rule that throws
Section titled “Every rule that throws”Each of these throws IllegalArgumentException when you build the selection, before anything reads the store:
| Method | Throws when |
|---|---|
by(...), all three, and not().by(...) |
No player, id or actor is named, or a person is both chosen and left out. |
in(...), and not().in(...) |
No world is named, or a world is both chosen and left out. |
kinds(...), not().kinds(...) |
No kind is named, or a kind is both chosen and left out. |
causes(...), not().causes(...) |
No cause is named, or a cause is both chosen and left out. |
materials(...), not().materials(...) |
No material is named, or a material is both chosen and left out. |
entities(...), not().entities(...) |
No entity type is named, or a type is both chosen and left out. |
matching(...), not().matching(...) |
No pattern is named, a value is not an id or a pattern of one, or an id is both chosen and left out. |
near(center, radius) |
The radius is negative. |
box(corner, opposite) |
The corners are in two worlds. |
near, box, at, in(name, box) |
The world is left out. |
since(time), until(time) |
since is after until. |
new Selection.Box(...) |
A lowest bound is above its highest. |
The message of each exception says what was wrong and how to fix it, so you can show it to the person who typed the value.
Reading a selection back
Section titled “Reading a selection back”A selection tells you what it chooses. Each getter returns an empty set or an empty Optional for a part you never set.
| Getter | Returns |
|---|---|
people() |
The ids chosen, or empty for everyone. |
since() |
The time set with since. Empty when unset, and empty while a within window is set. |
within() |
The window set with within, or empty. |
until() |
The time set with until, or empty. |
worlds() |
The stored world names, or empty for every world. |
box() |
The box inside the one world of worlds(), or empty for whole worlds. |
kinds(), causes() |
The kinds or causes chosen, or empty for all. |
materials() |
The namespaced ids and patterns chosen, or empty for all. |
left() |
What the selection leaves out, as a Selection.Left. |
left() returns Selection.Left(people, worlds, kinds, causes, materials). Each is a set, empty when nothing of that part is left out. people holds player ids and Actor.id() ids.
materials() and left().materials() hold namespaced ids. A Material.STONE reads back as minecraft:stone, and a pattern *_sign as minecraft:*_sign.