/*
* CraftBook Copyright (C) 2010-2017 sk89q <http://www.sk89q.com>
* CraftBook Copyright (C) 2011-2017 me4502 <http://www.me4502.com>
* CraftBook Copyright (C) Contributors
*
* This program 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.
*
* This program 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 this program. If not,
* see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.craftbook.sponge.mechanics.area.complex;
import static com.sk89q.craftbook.sponge.util.locale.TranslationsManager.USE_PERMISSIONS;
import com.google.inject.Inject;
import com.me4502.modularframework.module.Module;
import com.me4502.modularframework.module.guice.ModuleConfiguration;
import com.sk89q.craftbook.core.util.ConfigValue;
import com.sk89q.craftbook.core.util.CraftBookException;
import com.sk89q.craftbook.core.util.PermissionNode;
import com.sk89q.craftbook.core.util.documentation.DocumentationProvider;
import com.sk89q.craftbook.sponge.CraftBookPlugin;
import com.sk89q.craftbook.sponge.mechanics.area.complex.command.DeleteCommand;
import com.sk89q.craftbook.sponge.mechanics.area.complex.command.ListCommand;
import com.sk89q.craftbook.sponge.mechanics.area.complex.command.SaveCommand;
import com.sk89q.craftbook.sponge.mechanics.types.SpongeSignMechanic;
import com.sk89q.craftbook.sponge.util.BlockUtil;
import com.sk89q.craftbook.sponge.util.SignUtil;
import com.sk89q.craftbook.sponge.util.SpongePermissionNode;
import com.sk89q.craftbook.sponge.util.data.CraftBookKeys;
import com.sk89q.craftbook.sponge.util.data.mutable.LastPowerData;
import com.sk89q.craftbook.sponge.util.data.mutable.NamespaceData;
import ninja.leaping.configurate.ConfigurationNode;
import org.apache.commons.lang3.StringUtils;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.tileentity.Sign;
import org.spongepowered.api.command.CommandMapping;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.block.InteractBlockEvent;
import org.spongepowered.api.event.block.NotifyNeighborBlockEvent;
import org.spongepowered.api.event.block.tileentity.ChangeSignEvent;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.event.filter.cause.First;
import org.spongepowered.api.event.filter.cause.Named;
import org.spongepowered.api.service.permission.PermissionDescription;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.world.LocatableBlock;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import java.util.List;
import java.util.regex.Pattern;
@Module(id = "area", name = "Area", onEnable="onInitialize", onDisable="onDisable")
public class ComplexArea extends SpongeSignMechanic implements DocumentationProvider {
@Inject
@ModuleConfiguration
public ConfigurationNode config;
private SpongePermissionNode createPermissions = new SpongePermissionNode("craftbook.area", "Allows the user to create the ToggleArea mechanic.", PermissionDescription.ROLE_USER);
private SpongePermissionNode createSavePermissions = new SpongePermissionNode("craftbook.area.create.save", "Allows the user to create the ToggleArea Save-Only mechanic.", PermissionDescription.ROLE_USER);
private SpongePermissionNode createGlobalPermissions = new SpongePermissionNode("craftbook.area.create.global", "Allows the user to create global ToggleArea mechanics.", PermissionDescription.ROLE_STAFF);
private SpongePermissionNode createOtherPermissions = new SpongePermissionNode("craftbook.area.create.other", "Allows the user to create ToggleArea mechanics with other namespaces.", PermissionDescription.ROLE_STAFF);
private SpongePermissionNode usePermissions = new SpongePermissionNode("craftbook.area.use", "Allows the user to use the ToggleArea mechanic.", PermissionDescription.ROLE_USER);
private SpongePermissionNode commandSavePermissions = new SpongePermissionNode("craftbook.area.save", "Allows the user to save ToggleArea regions.", PermissionDescription.ROLE_USER);
public SpongePermissionNode commandSaveOtherPermissions = new SpongePermissionNode("craftbook.area.save.other", "Allows the user to save ToggleArea regions.", PermissionDescription.ROLE_STAFF);
public SpongePermissionNode commandSaveBypassLimitPermissions = new SpongePermissionNode("craftbook.area.save.bypass-limit", "Allows the user to bypass area limits.", PermissionDescription.ROLE_ADMIN);
private SpongePermissionNode commandDeletePermissions = new SpongePermissionNode("craftbook.area.delete", "Allows the user to delete ToggleArea regions.", PermissionDescription.ROLE_USER);
public SpongePermissionNode commandDeleteOtherPermissions = new SpongePermissionNode("craftbook.area.delete.other", "Allows the user to delete ToggleArea regions.", PermissionDescription.ROLE_STAFF);
private SpongePermissionNode commandListPermissions = new SpongePermissionNode("craftbook.area.list", "Allows the user to list ToggleArea regions.", PermissionDescription.ROLE_USER);
public SpongePermissionNode commandListOtherPermissions = new SpongePermissionNode("craftbook.area.list.other", "Allows the user to list ToggleArea regions.", PermissionDescription.ROLE_STAFF);
public ConfigValue<Integer> maxAreaSize = new ConfigValue<>("maximum-size", "The maximum amount of blocks that the saved areas can contain. -1 to disable limit.", 5000);
public ConfigValue<Integer> maxPerUser = new ConfigValue<>("maximum-per-user", "The maximum amount of areas that a namespace can own. 0 to disable limit.", 30);
public ConfigValue<Boolean> allowRedstone = new ConfigValue<>("allow-redstone", "Whether to allow redstone to be used to trigger this mechanic or not", true);
private CommandMapping areaCommandMapping;
private static final Pattern MARKER_PATTERN = Pattern.compile("^-[A-Za-z0-9_]*?-$");
@Override
public void onInitialize() throws CraftBookException {
super.onInitialize();
maxAreaSize.load(config);
maxPerUser.load(config);
allowRedstone.load(config);
CommandSpec saveCommand = CommandSpec.builder()
.description(Text.of("Command to save selected WorldEdit regions."))
.permission(commandSavePermissions.getNode())
.executor(new SaveCommand(this))
.arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("id"))), GenericArguments.optional(GenericArguments.onlyOne(GenericArguments.string(Text.of("namespace")))))
.build();
CommandSpec deleteCommand = CommandSpec.builder()
.description(Text.of("Command to delete saved ToggleArea regions."))
.permission(commandDeletePermissions.getNode())
.executor(new DeleteCommand(this))
.arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("id"))), GenericArguments.optional(GenericArguments.onlyOne(GenericArguments.string(Text.of("namespace")))))
.build();
CommandSpec listCommand = CommandSpec.builder()
.description(Text.of("Command to list saved ToggleArea regions."))
.permission(commandListPermissions.getNode())
.executor(new ListCommand(this))
.arguments(GenericArguments.optional(GenericArguments.onlyOne(GenericArguments.string(Text.of("namespace")))))
.build();
CommandSpec mainAreaCommand = CommandSpec.builder()
.description(Text.of("Base command for the ToggleArea system."))
.child(saveCommand, "save")
.child(deleteCommand, "delete", "del")
.child(listCommand, "list")
.build();
areaCommandMapping = Sponge.getCommandManager().register(CraftBookPlugin.inst(), mainAreaCommand, "area", "togglearea").orElse(null);
}
@Override
public void onDisable() {
super.onDisable();
if (areaCommandMapping != null) {
Sponge.getCommandManager().removeMapping(areaCommandMapping);
}
}
@Listener
public void onSignChange(ChangeSignEvent event, @Named(NamedCause.SOURCE) Player player) {
for(String line : getValidSigns()) {
if(SignUtil.getTextRaw(event.getText(), 1).equalsIgnoreCase(line)) {
if(!createPermissions.hasPermission(player) && !("[SaveArea]".equals(line) && createSavePermissions.hasPermission(player))) {
player.sendMessage(Text.of(TextColors.RED, "You do not have permission to create this mechanic!"));
event.setCancelled(true);
} else {
List<Text> lines = event.getText().lines().get();
lines.set(1, Text.of(line));
String namespace = lines.get(0).toPlain();
boolean personal = false;
if ("global".equals(namespace.toLowerCase())) {
if (!createGlobalPermissions.hasPermission(player)) {
player.sendMessage(Text.of(TextColors.RED, "You do not have permission to create global ToggleAreas!"));
event.setCancelled(true);
return;
} else {
namespace = "GLOBAL";
}
} else if (namespace.trim().isEmpty() || !createOtherPermissions.hasPermission(player)) {
namespace = player.getUniqueId().toString();
personal = true;
}
lines.set(0, Text.of(personal ? player.getName() : namespace));
if(!isValidArea(namespace, lines.get(2).toPlain(), lines.get(3).toPlain())) {
player.sendMessage(Text.of(TextColors.RED, "This area is missing!"));
event.setCancelled(true);
return;
}
event.getTargetTile().offer(new NamespaceData(namespace));
event.getText().set(Keys.SIGN_LINES, lines);
player.sendMessage(Text.of(TextColors.YELLOW, "Created ToggleArea!"));
}
break;
}
}
}
@Listener
public void onBlockUpdate(NotifyNeighborBlockEvent event, @First LocatableBlock source) {
if(!allowRedstone.getValue())
return;
if(!SignUtil.isSign(source.getLocation())) return;
Location<World> block = source.getLocation();
Sign sign = (Sign) block.getTileEntity().get();
if (isMechanicSign(sign)) {
Player player = event.getCause().get(NamedCause.SOURCE, Player.class).orElse(null);
if(player != null) {
if(!usePermissions.hasPermission(player)) {
player.sendMessage(USE_PERMISSIONS);
return;
}
}
boolean isPowered = BlockUtil.getBlockPowerLevel(block).orElse(0) > 0;
boolean wasPowered = block.get(CraftBookKeys.LAST_POWER).orElse(0) > 0;
if (isPowered != wasPowered) {
triggerMechanic(block, sign, isPowered);
block.offer(new LastPowerData(isPowered ? 15 : 0));
}
}
}
@Listener
public void onRightClick(InteractBlockEvent.Secondary.MainHand event, @Named(NamedCause.SOURCE) Player human) {
event.getTargetBlock().getLocation().ifPresent(location -> {
if (isValid(location)) {
location.getTileEntity().ifPresent((sign -> {
if (human == null || usePermissions.hasPermission(human)) {
if (triggerMechanic(location, (Sign) sign, null)) {
event.setCancelled(true);
if (human != null)
human.sendMessage(Text.of(TextColors.YELLOW, "Toggled!"));
}
} else {
human.sendMessage(USE_PERMISSIONS);
}
}));
}
});
}
public boolean triggerMechanic(Location<World> location, Sign sign, Boolean forceState) {
boolean save = "[SaveArea]".equals(SignUtil.getTextRaw(sign, 1));
String namespace = sign.get(CraftBookKeys.NAMESPACE).orElse(null);
String id = StringUtils.replace(SignUtil.getTextRaw(sign, 2), "-", "").toLowerCase();
String inactiveID = StringUtils.replace(SignUtil.getTextRaw(sign, 3), "-", "").toLowerCase();
if (namespace == null) {
return false;
}
try {
CuboidCopy copy;
boolean state = checkToggleState(sign);
if (forceState != null) {
state = forceState;
}
if (state) {
copy = CopyManager.load(location.getExtent(), namespace, id);
if (save) {
copy.copy();
CopyManager.save(namespace, id, copy);
}
if (!inactiveID.isEmpty() && !"--".equals(inactiveID)) {
copy = CopyManager.load(location.getExtent(), namespace, inactiveID);
copy.paste();
} else {
copy.clear();
}
setToggleState(sign, false);
} else {
if (save && !inactiveID.isEmpty() && !"--".equals(inactiveID)) {
copy = CopyManager.load(location.getExtent(), namespace, inactiveID);
copy.copy();
CopyManager.save(namespace, inactiveID, copy);
}
copy = CopyManager.load(location.getExtent(), namespace, id);
copy.paste();
setToggleState(sign, true);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private static boolean isValidArea(String namespace, String areaOn, String areaOff) {
if (CopyManager.isExistingArea(CraftBookPlugin.inst().getWorkingDirectory(), namespace, areaOn)) {
if (areaOff == null || areaOff.isEmpty() || "--".equals(areaOff)) return true;
if (CopyManager.isExistingArea(CraftBookPlugin.inst().getWorkingDirectory(), namespace, areaOff)) return true;
}
return false;
}
private static boolean checkToggleState(Sign sign) {
String line3 = SignUtil.getTextRaw(sign, 2).toLowerCase();
String line4 = SignUtil.getTextRaw(sign, 3).toLowerCase();
return MARKER_PATTERN.matcher(line3).matches() || !("--".equals(line4) || MARKER_PATTERN.matcher(line4).matches());
}
private static void setToggleState(Sign sign, boolean state) {
int toToggleOn = state ? 2 : 3;
int toToggleOff = state ? 3 : 2;
List<Text> lines = sign.lines().get();
lines.set(toToggleOff, Text.of(sign.lines().get(toToggleOff).toPlain().replace("-", "")));
lines.set(toToggleOn, Text.of("-", sign.lines().get(toToggleOn), "-"));
sign.offer(Keys.SIGN_LINES, lines);
}
public String[] getValidSigns() {
return new String[]{
"[SaveArea]",
"[Area]"
};
}
@Override
public String getName() {
return "ToggleArea";
}
@Override
public SpongePermissionNode getCreatePermission() {
return createPermissions;
}
@Override
public String getPath() {
return "mechanics/togglearea";
}
@Override
public ConfigValue<?>[] getConfigurationNodes() {
return new ConfigValue<?>[]{
maxAreaSize,
maxPerUser,
allowRedstone
};
}
@Override
public PermissionNode[] getPermissionNodes() {
return new PermissionNode[]{
createPermissions,
usePermissions,
createSavePermissions,
createGlobalPermissions,
createOtherPermissions,
commandSavePermissions,
commandSaveOtherPermissions,
commandSaveBypassLimitPermissions,
commandListPermissions,
commandListOtherPermissions,
commandDeletePermissions,
commandDeleteOtherPermissions
};
}
}