baritone/src/main/java/baritone/command/defaults/WaypointsCommand.java

417 lines
20 KiB
Java

/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.command.defaults;
import baritone.Baritone;
import baritone.api.IBaritone;
import baritone.api.cache.IWaypoint;
import baritone.api.cache.IWorldData;
import baritone.api.cache.Waypoint;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
import baritone.api.command.datatypes.ForWaypoints;
import baritone.api.command.datatypes.RelativeBlockPos;
import baritone.api.command.exception.CommandException;
import baritone.api.command.exception.CommandInvalidStateException;
import baritone.api.command.exception.CommandInvalidTypeException;
import baritone.api.command.helpers.Paginator;
import baritone.api.command.helpers.TabCompleteHelper;
import baritone.api.pathing.goals.Goal;
import baritone.api.pathing.goals.GoalBlock;
import baritone.api.utils.BetterBlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.event.ClickEvent;
import net.minecraft.util.text.event.HoverEvent;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
public class WaypointsCommand extends Command {
private Map<IWorldData, List<IWaypoint>> deletedWaypoints = new HashMap<>();
public WaypointsCommand(IBaritone baritone) {
super(baritone, "waypoints", "waypoint", "wp");
}
@Override
public void execute(String label, IArgConsumer args) throws CommandException {
Action action = args.hasAny() ? Action.getByName(args.getString()) : Action.LIST;
if (action == null) {
throw new CommandInvalidTypeException(args.consumed(), "an action");
}
BiFunction<IWaypoint, Action, ITextComponent> toComponent = (waypoint, _action) -> {
ITextComponent component = new TextComponentString("");
ITextComponent tagComponent = new TextComponentString(waypoint.getTag().name() + " ");
tagComponent.getStyle().setColor(TextFormatting.GRAY);
String name = waypoint.getName();
ITextComponent nameComponent = new TextComponentString(!name.isEmpty() ? name : "<empty>");
nameComponent.getStyle().setColor(!name.isEmpty() ? TextFormatting.GRAY : TextFormatting.DARK_GRAY);
ITextComponent timestamp = new TextComponentString(" @ " + new Date(waypoint.getCreationTimestamp()));
timestamp.getStyle().setColor(TextFormatting.DARK_GRAY);
component.appendSibling(tagComponent);
component.appendSibling(nameComponent);
component.appendSibling(timestamp);
component.getStyle()
.setHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new TextComponentString("Click to select")
))
.setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s %s %s @ %d",
FORCE_COMMAND_PREFIX,
label,
_action.names[0],
waypoint.getTag().getName(),
waypoint.getCreationTimestamp()
))
);
return component;
};
Function<IWaypoint, ITextComponent> transform = waypoint ->
toComponent.apply(waypoint, action == Action.LIST ? Action.INFO : action);
if (action == Action.LIST) {
IWaypoint.Tag tag = args.hasAny() ? IWaypoint.Tag.getByName(args.peekString()) : null;
if (tag != null) {
args.get();
}
IWaypoint[] waypoints = tag != null
? ForWaypoints.getWaypointsByTag(this.baritone, tag)
: ForWaypoints.getWaypoints(this.baritone);
if (waypoints.length > 0) {
args.requireMax(1);
Paginator.paginate(
args,
waypoints,
() -> logDirect(
tag != null
? String.format("All waypoints by tag %s:", tag.name())
: "All waypoints:"
),
transform,
String.format(
"%s%s %s%s",
FORCE_COMMAND_PREFIX,
label,
action.names[0],
tag != null ? " " + tag.getName() : ""
)
);
} else {
args.requireMax(0);
throw new CommandInvalidStateException(
tag != null
? "No waypoints found by that tag"
: "No waypoints found"
);
}
} else if (action == Action.SAVE) {
IWaypoint.Tag tag = args.hasAny() ? IWaypoint.Tag.getByName(args.peekString()) : null;
if (tag == null) {
tag = IWaypoint.Tag.USER;
} else {
args.get();
}
String name = (args.hasExactlyOne() || args.hasExactly(4)) ? args.getString() : "";
BetterBlockPos pos = args.hasAny()
? args.getDatatypePost(RelativeBlockPos.INSTANCE, ctx.playerFeet())
: ctx.playerFeet();
args.requireMax(0);
IWaypoint waypoint = new Waypoint(name, tag, pos);
ForWaypoints.waypoints(this.baritone).addWaypoint(waypoint);
ITextComponent component = new TextComponentString("Waypoint added: ");
component.getStyle().setColor(TextFormatting.GRAY);
component.appendSibling(toComponent.apply(waypoint, Action.INFO));
logDirect(component);
} else if (action == Action.CLEAR) {
args.requireMax(1);
String name = args.getString();
IWaypoint.Tag tag = IWaypoint.Tag.getByName(name);
if (tag == null) {
throw new CommandInvalidStateException("Invalid tag, \"" + name + "\"");
}
IWaypoint[] waypoints = ForWaypoints.getWaypointsByTag(this.baritone, tag);
for (IWaypoint waypoint : waypoints) {
ForWaypoints.waypoints(this.baritone).removeWaypoint(waypoint);
}
deletedWaypoints.computeIfAbsent(baritone.getWorldProvider().getCurrentWorld(), k -> new ArrayList<>()).addAll(Arrays.<IWaypoint>asList(waypoints));
ITextComponent textComponent = new TextComponentString(String.format("Cleared %d waypoints, click to restore them", waypoints.length));
textComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s restore @ %s",
FORCE_COMMAND_PREFIX,
label,
Stream.of(waypoints).map(wp -> Long.toString(wp.getCreationTimestamp())).collect(Collectors.joining(" "))
)
));
logDirect(textComponent);
} else if (action == Action.RESTORE) {
List<IWaypoint> waypoints = new ArrayList<>();
List<IWaypoint> deletedWaypoints = this.deletedWaypoints.getOrDefault(baritone.getWorldProvider().getCurrentWorld(), Collections.emptyList());
if (args.peekString().equals("@")) {
args.get();
// no args.requireMin(1) because if the user clears an empty tag there is nothing to restore
while (args.hasAny()) {
long timestamp = args.getAs(Long.class);
for (IWaypoint waypoint : deletedWaypoints) {
if (waypoint.getCreationTimestamp() == timestamp) {
waypoints.add(waypoint);
break;
}
}
}
} else {
args.requireExactly(1);
int size = deletedWaypoints.size();
int amount = Math.min(size, args.getAs(Integer.class));
waypoints = new ArrayList<>(deletedWaypoints.subList(size - amount, size));
}
waypoints.forEach(ForWaypoints.waypoints(this.baritone)::addWaypoint);
deletedWaypoints.removeIf(waypoints::contains);
logDirect(String.format("Restored %d waypoints", waypoints.size()));
} else {
IWaypoint[] waypoints = args.getDatatypeFor(ForWaypoints.INSTANCE);
IWaypoint waypoint = null;
if (args.hasAny() && args.peekString().equals("@")) {
args.requireExactly(2);
args.get();
long timestamp = args.getAs(Long.class);
for (IWaypoint iWaypoint : waypoints) {
if (iWaypoint.getCreationTimestamp() == timestamp) {
waypoint = iWaypoint;
break;
}
}
if (waypoint == null) {
throw new CommandInvalidStateException("Timestamp was specified but no waypoint was found");
}
} else {
switch (waypoints.length) {
case 0:
throw new CommandInvalidStateException("No waypoints found");
case 1:
waypoint = waypoints[0];
break;
default:
break;
}
}
if (waypoint == null) {
args.requireMax(1);
Paginator.paginate(
args,
waypoints,
() -> logDirect("Multiple waypoints were found:"),
transform,
String.format(
"%s%s %s %s",
FORCE_COMMAND_PREFIX,
label,
action.names[0],
args.consumedString()
)
);
} else {
if (action == Action.INFO) {
logDirect(transform.apply(waypoint));
logDirect(String.format("Position: %s", waypoint.getLocation()));
ITextComponent deleteComponent = new TextComponentString("Click to delete this waypoint");
deleteComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s delete %s @ %d",
FORCE_COMMAND_PREFIX,
label,
waypoint.getTag().getName(),
waypoint.getCreationTimestamp()
)
));
ITextComponent goalComponent = new TextComponentString("Click to set goal to this waypoint");
goalComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s goal %s @ %d",
FORCE_COMMAND_PREFIX,
label,
waypoint.getTag().getName(),
waypoint.getCreationTimestamp()
)
));
ITextComponent recreateComponent = new TextComponentString("Click to show a command to recreate this waypoint");
recreateComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.SUGGEST_COMMAND,
String.format(
"%s%s save %s %s %s %s %s",
Baritone.settings().prefix.value, // This uses the normal prefix because it is run by the user.
label,
waypoint.getTag().getName(),
waypoint.getName(),
waypoint.getLocation().x,
waypoint.getLocation().y,
waypoint.getLocation().z
)
));
ITextComponent backComponent = new TextComponentString("Click to return to the waypoints list");
backComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s list",
FORCE_COMMAND_PREFIX,
label
)
));
logDirect(deleteComponent);
logDirect(goalComponent);
logDirect(recreateComponent);
logDirect(backComponent);
} else if (action == Action.DELETE) {
ForWaypoints.waypoints(this.baritone).removeWaypoint(waypoint);
deletedWaypoints.computeIfAbsent(baritone.getWorldProvider().getCurrentWorld(), k -> new ArrayList<>()).add(waypoint);
ITextComponent textComponent = new TextComponentString("That waypoint has successfully been deleted, click to restore it");
textComponent.getStyle().setClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
String.format(
"%s%s restore @ %s",
FORCE_COMMAND_PREFIX,
label,
waypoint.getCreationTimestamp()
)
));
logDirect(textComponent);
} else if (action == Action.GOAL) {
Goal goal = new GoalBlock(waypoint.getLocation());
baritone.getCustomGoalProcess().setGoal(goal);
logDirect(String.format("Goal: %s", goal));
} else if (action == Action.GOTO) {
Goal goal = new GoalBlock(waypoint.getLocation());
baritone.getCustomGoalProcess().setGoalAndPath(goal);
logDirect(String.format("Going to: %s", goal));
}
}
}
}
@Override
public Stream<String> tabComplete(String label, IArgConsumer args) throws CommandException {
if (args.hasAny()) {
if (args.hasExactlyOne()) {
return new TabCompleteHelper()
.append(Action.getAllNames())
.sortAlphabetically()
.filterPrefix(args.getString())
.stream();
} else {
Action action = Action.getByName(args.getString());
if (args.hasExactlyOne()) {
if (action == Action.LIST || action == Action.SAVE || action == Action.CLEAR) {
return new TabCompleteHelper()
.append(IWaypoint.Tag.getAllNames())
.sortAlphabetically()
.filterPrefix(args.getString())
.stream();
} else if (action == Action.RESTORE) {
return Stream.empty();
} else {
return args.tabCompleteDatatype(ForWaypoints.INSTANCE);
}
} else if (args.has(3) && action == Action.SAVE) {
args.get();
args.get();
return args.tabCompleteDatatype(RelativeBlockPos.INSTANCE);
}
}
}
return Stream.empty();
}
@Override
public String getShortDesc() {
return "Manage waypoints";
}
@Override
public List<String> getLongDesc() {
return Arrays.asList(
"The waypoint command allows you to manage Baritone's waypoints.",
"",
"Waypoints can be used to mark positions for later. Waypoints are each given a tag and an optional name.",
"",
"Note that the info, delete, and goal commands let you specify a waypoint by tag. If there is more than one waypoint with a certain tag, then they will let you select which waypoint you mean.",
"",
"Missing arguments for the save command use the USER tag, creating an unnamed waypoint and your current position as defaults.",
"",
"Usage:",
"> wp [l/list] - List all waypoints.",
"> wp <l/list> <tag> - List all waypoints by tag.",
"> wp <s/save> - Save an unnamed USER waypoint at your current position",
"> wp <s/save> [tag] [name] [pos] - Save a waypoint with the specified tag, name and position.",
"> wp <i/info/show> <tag/name> - Show info on a waypoint by tag or name.",
"> wp <d/delete> <tag/name> - Delete a waypoint by tag or name.",
"> wp <restore> <n> - Restore the last n deleted waypoints.",
"> wp <c/clear> <tag> - Delete all waypoints with the specified tag.",
"> wp <g/goal> <tag/name> - Set a goal to a waypoint by tag or name.",
"> wp <goto> <tag/name> - Set a goal to a waypoint by tag or name and start pathing."
);
}
private enum Action {
LIST("list", "get", "l"),
CLEAR("clear", "c"),
SAVE("save", "s"),
INFO("info", "show", "i"),
DELETE("delete", "d"),
RESTORE("restore"),
GOAL("goal", "g"),
GOTO("goto");
private final String[] names;
Action(String... names) {
this.names = names;
}
public static Action getByName(String name) {
for (Action action : Action.values()) {
for (String alias : action.names) {
if (alias.equalsIgnoreCase(name)) {
return action;
}
}
}
return null;
}
public static String[] getAllNames() {
Set<String> names = new HashSet<>();
for (Action action : Action.values()) {
names.addAll(Arrays.asList(action.names));
}
return names.toArray(new String[0]);
}
}
}