Add RegionForSale import, rental auto-extend and other things

- `/as import RegionForSale`, as requested in #98:
    - Imports all regions from RegionForSale
    - Converts almost all settings to their AreaShop equivalent
    - Handles parent regions
- Add `rent.autoExtend` for use with rental regions to automatically extend the rent when the time is out, start of #29, still need the command
    - Vault compatible permissions plugin is required to let this work while the player is offline
    - All permissions, limits and the player balance are checked as during a normal rent
- Add option to region groups to automatically add all regions of a certain world
- BREAKING: Region events now use OfflinePlayer instead of Player
- Automatically import existing region owners/members when adding a region to AreaShop

There are still a couple of things missing in the import and it needs to be tested a lot.
This commit is contained in:
Thijs Wiefferink 2017-10-20 13:40:53 +02:00
parent 0bf2314f2a
commit b1dfe93088
29 changed files with 1017 additions and 194 deletions

View File

@ -24,6 +24,7 @@ import net.milkbowl.vault.economy.Economy;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
@ -34,6 +35,7 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -521,15 +523,37 @@ public final class AreaShop extends JavaPlugin implements AreaShopInterface {
* Get the Vault permissions provider.
* @return Vault permissions provider
*/
public net.milkbowl.vault.permission.Permission getPermissionProvider() {
public @Nullable net.milkbowl.vault.permission.Permission getPermissionProvider() {
RegisteredServiceProvider<net.milkbowl.vault.permission.Permission> permissionProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class);
if (permissionProvider == null || permissionProvider.getProvider() == null) {
error("There is no permission plugin that supports Vault, make sure you have a Vault-compatible permissions plugin installed");
return null;
}
return permissionProvider.getProvider();
}
/**
* Check for a permission of a (possibly offline) player.
* @param offlinePlayer OfflinePlayer to check
* @param permission Permission to check
* @return true if the player has the permission, false if the player does not have permission or, is offline and there is not Vault-compatible permission plugin
*/
public boolean hasPermission(OfflinePlayer offlinePlayer, String permission) {
// Online, return through Bukkit
if(offlinePlayer.getPlayer() != null) {
return offlinePlayer.getPlayer().hasPermission(permission);
}
// Resolve while offline if possible
net.milkbowl.vault.permission.Permission permissionProvider = getPermissionProvider();
if(permissionProvider != null) {
// TODO: Should we provide a world here?
return permissionProvider.playerHas(null, offlinePlayer, permission);
}
// Player offline and no offline permission provider available, safely say that there is no permission
return false;
}
/**
* Method to get the FileManager (loads/save regions and can be used to get regions).
* @return The fileManager

View File

@ -15,8 +15,10 @@ import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
public class AddCommand extends CommandAreaShop {
@ -140,6 +142,9 @@ public class AddCommand extends CommandAreaShop {
boolean landlord = (!sender.hasPermission("areashop.create" + type)
&& ((sender.hasPermission("areashop.create" + type + ".owner") && isOwner)
|| (sender.hasPermission("areashop.create" + type + ".member") && isMember)));
List<UUID> existing = new ArrayList<>();
existing.addAll(plugin.getWorldGuardHandler().getOwners(region).asUniqueIdList());
existing.addAll(plugin.getWorldGuardHandler().getMembers(region).asUniqueIdList());
if(isRent) {
RentRegion rent = new RentRegion(region.getId(), finalWorld);
// Set landlord
@ -155,6 +160,33 @@ public class AddCommand extends CommandAreaShop {
// Run commands
rent.runEventCommands(GeneralRegion.RegionEvent.CREATED, false);
// Add existing owners/members if any
if(!landlord && !existing.isEmpty()) {
// TODO also execute rent events to notify other plugins?
// Run commands
rent.runEventCommands(GeneralRegion.RegionEvent.RENTED, true);
// Add values to the rent and send it to FileManager
rent.setRentedUntil(Calendar.getInstance().getTimeInMillis() + rent.getDuration());
rent.setRenter(existing.remove(0));
rent.updateLastActiveTime();
// Add others as friends
for(UUID friend : existing) {
rent.getFriendsFeature().addFriend(friend, null);
}
// Fire schematic event and updated times extended
rent.handleSchematicEvent(GeneralRegion.RegionEvent.RENTED);
// Notify about updates
rent.update();
rent.runEventCommands(GeneralRegion.RegionEvent.RENTED, false);
}
regionsSuccess.add(rent);
} else {
BuyRegion buy = new BuyRegion(region.getId(), finalWorld);
@ -172,6 +204,32 @@ public class AddCommand extends CommandAreaShop {
// Run commands
buy.runEventCommands(GeneralRegion.RegionEvent.CREATED, false);
// Add existing owners/members if any
if(!landlord && !existing.isEmpty()) {
// TODO also execute buy events to notify for other plugins?
// Run commands
buy.runEventCommands(GeneralRegion.RegionEvent.BOUGHT, true);
// Set the owner
buy.setBuyer(existing.remove(0));
buy.updateLastActiveTime();
// Add others as friends
for(UUID friend : existing) {
buy.getFriendsFeature().addFriend(friend, null);
}
// Notify about updates
buy.update();
// Update everything
buy.handleSchematicEvent(GeneralRegion.RegionEvent.BOUGHT);
// Run commands
buy.runEventCommands(GeneralRegion.RegionEvent.BOUGHT, false);
}
regionsSuccess.add(buy);
}
}

View File

@ -6,6 +6,7 @@ import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class GroupinfoCommand extends CommandAreaShop {
@ -38,7 +39,7 @@ public class GroupinfoCommand extends CommandAreaShop {
plugin.message(sender, "groupinfo-noGroup", args[1]);
return;
}
List<String> members = group.getMembers();
Set<String> members = group.getMembers();
if(members.size() == 0) {
plugin.message(sender, "groupinfo-noMembers", group.getName());
} else {

View File

@ -0,0 +1,74 @@
package me.wiefferink.areashop.commands;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
public class ImportCommand extends CommandAreaShop {
/* RegionGroup priority usage:
0: Settings from /config.yml
1: Settings from /worlds/<world>/config.yml
2: Settings from /worlds/<world>/parent-regions.yml (if their priority is set it is added to this value)
*/
@Override
public String getCommandStart() {
return "areashop import";
}
@Override
public String getHelp(CommandSender target) {
if(target.hasPermission("areashop.import")) {
return "help-import";
}
return null;
}
// TODO
// - Landlord?
// - Friends
// - Region flags?
// - Settings from the 'permissions' section in RegionForSale/config.yml?
@Override
public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("areashop.import")) {
plugin.message(sender, "import-noPermission");
return;
}
if(args.length < 2) {
plugin.message(sender, "import-help");
return;
}
if(!"RegionForSale".equalsIgnoreCase(args[1])) {
plugin.message(sender, "import-wrongSource");
return;
}
ImportJob importJob = new ImportJob(sender);
}
@Override
public List<String> getTabCompleteList(int toComplete, String[] start, CommandSender sender) {
List<String> result = new ArrayList<>();
if(toComplete == 2) {
result.add("RegionForSale");
}
return result;
}
}

View File

@ -0,0 +1,456 @@
package me.wiefferink.areashop.commands;
import com.google.common.base.Charsets;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import javafx.util.Pair;
import me.wiefferink.areashop.AreaShop;
import me.wiefferink.areashop.features.SignsFeature;
import me.wiefferink.areashop.regions.BuyRegion;
import me.wiefferink.areashop.regions.GeneralRegion;
import me.wiefferink.areashop.regions.RegionGroup;
import me.wiefferink.areashop.regions.RentRegion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ImportJob {
private AreaShop plugin;
private CommandSender sender;
/**
* Create and execute the import.
* @param sender CommandSender that should receive progress updates
*/
public ImportJob(CommandSender sender) {
this.sender = sender;
this.plugin = AreaShop.getInstance();
execute();
}
/**
* Execute the job.
*/
private void execute() {
// Check for RegionForSale data
File regionForSaleFolder = new File(plugin.getDataFolder().getParentFile().getAbsolutePath(), "RegionForSale");
if(!regionForSaleFolder.exists()) {
message("import-noPluginFolder", regionForSaleFolder.getName());
return;
}
File worldsFolder = new File(regionForSaleFolder.getAbsolutePath(), "worlds");
if(!worldsFolder.exists()) {
message("import-noWorldsFolder");
return;
}
File[] worldFolders = worldsFolder.listFiles();
if(worldFolders == null) {
message("import-noWorldsFolder");
return;
}
// Import data for each world
message("import-start");
// Group with settings for all imported regions
RegionGroup regionForSaleGroup = new RegionGroup(plugin, "RegionForSale");
plugin.getFileManager().addGroup(regionForSaleGroup);
// Import /RegionForSale/config.yml settings
File regionForSaleConfigFile = new File(regionForSaleFolder.getAbsolutePath(), "config.yml");
YamlConfiguration regionForSaleConfig = loadConfiguration(regionForSaleConfigFile);
if(regionForSaleConfig == null) {
messageNoPrefix("import-loadConfigFailed", regionForSaleConfigFile.getAbsolutePath());
} else {
importRegionSettings(regionForSaleConfig, regionForSaleGroup.getSettings(), null);
regionForSaleGroup.setSetting("priority", 0);
}
// Import /RegionForSale/general.yml settings
File regionForSaleGeneralFile = new File(regionForSaleFolder.getAbsolutePath(), "config.yml");
YamlConfiguration regionForSaleGeneral = loadConfiguration(regionForSaleConfigFile);
if(regionForSaleGeneral == null) {
messageNoPrefix("import-loadConfigFailed", regionForSaleGeneralFile.getAbsolutePath());
} else {
// Collection interval of RegionForSale maps to rent duration
String duration = "1 day";
if(regionForSaleGeneral.isLong("interval.collect_money")) {
duration = minutesToString(regionForSaleGeneral.getLong("interval.collect_money"));
}
regionForSaleGroup.setSetting("rent.duration", duration);
// Global economy account has an effect close to landlord in AreaShop
if(regionForSaleGeneral.isString("global_econ_account")) {
regionForSaleGroup.setSetting("general.landlordName", regionForSaleGeneral.getString("global_econ_account"));
}
}
regionForSaleGroup.saveRequired();
////////// Handle defaults of RegionForSale
// Set autoExtend, to keep the same behavior as RegionForSale had
regionForSaleGroup.setSetting("rent.autoExtend", true);
// Import regions from each world
for(File worldFolder : worldFolders) {
// Skip files
if(!worldFolder.isDirectory()) {
continue;
}
messageNoPrefix("import-doWorld", worldFolder.getName());
// Get the Bukkit world
World world = Bukkit.getWorld(worldFolder.getName());
if(world == null) {
messageNoPrefix("import-noBukkitWorld");
continue;
}
// Get the WorldGuard RegionManager
RegionManager regionManager = plugin.getWorldGuard().getRegionManager(world);
if(regionManager == null) {
messageNoPrefix("import-noRegionManger");
continue;
}
// Load the /worlds/<world>/regions.yml file
File regionsFile = new File(worldFolder.getAbsolutePath(), "regions.yml");
YamlConfiguration regions = loadConfiguration(regionsFile);
if(regions == null) {
messageNoPrefix("import-loadRegionsFailed", regionsFile.getAbsolutePath());
continue;
}
// Load /worlds/<world>/config.yml file
File worldConfigFile = new File(worldFolder.getAbsolutePath(), "config.yml");
YamlConfiguration worldConfig = loadConfiguration(worldConfigFile);
if(worldConfig == null) {
messageNoPrefix("import-loadWorldConfigFailed", worldConfigFile.getAbsolutePath());
// Simply skip importing the settings, since this is not really fatal
} else {
// RegionGroup with all world settings
RegionGroup worldGroup = new RegionGroup(plugin, "RegionForSale-" + worldFolder.getName());
importRegionSettings(worldConfig, worldGroup.getSettings(), null);
worldGroup.setSetting("priority", 1);
worldGroup.addWorld(worldFolder.getName());
plugin.getFileManager().addGroup(regionForSaleGroup);
worldGroup.saveRequired();
}
// Create groups to hold settings of /worlds/<world>/parent-regions.yml
File parentRegionsFile = new File(worldFolder.getAbsolutePath(), "parent-regions.yml");
YamlConfiguration parentRegions = loadConfiguration(parentRegionsFile);
if(parentRegions == null) {
messageNoPrefix("import-loadParentRegionsFailed", parentRegionsFile.getAbsolutePath());
// Non-fatal, so just continue
} else {
for(String parentRegionName : parentRegions.getKeys(false)) {
// Get WorldGuard region
ProtectedRegion worldGuardRegion = regionManager.getRegion(parentRegionName);
if(worldGuardRegion == null) {
messageNoPrefix("import-noWorldGuardRegionParent", parentRegionName);
continue;
}
// Get settings section
ConfigurationSection parentRegionSection = parentRegions.getConfigurationSection(parentRegionName);
if(parentRegionSection == null) {
messageNoPrefix("import-improperParentRegion", parentRegionName);
continue;
}
// Skip if it does not have any settings
if(parentRegionSection.getKeys(false).isEmpty()) {
continue;
}
// Import parent region settings into a RegionGroup
RegionGroup parentRegionGroup = new RegionGroup(plugin, "RegionForSale-" + worldFolder.getName() + "-" + parentRegionName);
importRegionSettings(parentRegionSection, parentRegionGroup.getSettings(), null);
parentRegionGroup.setSetting("priority", 2 + parentRegionSection.getLong("info.priority", 0));
parentRegionGroup.saveRequired();
// TODO add all regions that are contained in this parent region
// Utils.getWorldEditRegionsInSelection()
}
}
// Read and import regions
for(String regionKey : regions.getKeys(false)) {
GeneralRegion existingRegion = plugin.getFileManager().getRegion(regionKey);
if(existingRegion != null) {
if(world.getName().equalsIgnoreCase(existingRegion.getWorldName())) {
messageNoPrefix("import-alreadyAdded", regionKey);
} else {
messageNoPrefix("import-alreadyAddedOtherWorld", regionKey, existingRegion.getWorldName(), world.getName());
}
continue;
}
ConfigurationSection regionSection = regions.getConfigurationSection(regionKey);
if(regionSection == null) {
messageNoPrefix("import-invalidRegionSection", regionKey);
continue;
}
// Get WorldGuard region
ProtectedRegion worldGuardRegion = regionManager.getRegion(regionKey);
if(worldGuardRegion == null) {
messageNoPrefix("import-noWorldGuardRegion", regionKey);
continue;
}
String owner = regionSection.getString("info.owner", null);
boolean isBought = regionSection.getBoolean("info.is-bought");
boolean rentable = regionSection.getBoolean("economic-settings.rentable");
boolean buyable = regionSection.getBoolean("economic-settings.buyable");
// Can be bought and rented, import as buy
if(buyable && rentable) {
messageNoPrefix("import-buyAndRent", regionKey);
}
// Cannot be bought or rented, skip
if(!buyable && !rentable && owner == null) {
messageNoPrefix("import-noBuyAndNoRent", regionKey);
continue;
}
// Create region
GeneralRegion region;
if(rentable || (owner != null && !isBought)) {
region = new RentRegion(regionKey, world);
plugin.getFileManager().addRent((RentRegion)region);
} else {
region = new BuyRegion(regionKey, world);
plugin.getFileManager().addBuy((BuyRegion)region);
}
// Import settings
importRegionSettings(regionSection, region.getConfig(), region);
region.getConfig().set("general.importedFrom", "RegionForSale");
// Get existing owners and members
List<UUID> existing = new ArrayList<>();
if(owner != null) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(owner);
if(offlinePlayer != null) {
existing.add(offlinePlayer.getUniqueId());
}
}
for(UUID uuid : plugin.getWorldGuardHandler().getOwners(worldGuardRegion).asUniqueIdList()) {
if(!existing.contains(uuid)) {
existing.add(uuid);
}
}
for(UUID uuid : plugin.getWorldGuardHandler().getMembers(worldGuardRegion).asUniqueIdList()) {
if(!existing.contains(uuid)) {
existing.add(uuid);
}
}
// First owner (or if none, the first member) will be the renter/buyer
if(!existing.isEmpty()) {
region.setOwner(existing.remove(0));
}
// Add others as friends
for(UUID friend : existing) {
region.getFriendsFeature().addFriend(friend, null);
}
region.saveRequired();
messageNoPrefix("import-imported", regionKey);
}
}
// Update all regions
plugin.getFileManager().updateAllRegions(sender);
// Write all imported regions and settings to disk
plugin.getFileManager().saveRequiredFiles();
}
/**
* Load a YamlConfiguration from disk using UTF-8 encoding.
* @param from File to read the configuration from
* @return YamlConfiguration if the file exists and got read wihout problems, otherwise null
*/
private YamlConfiguration loadConfiguration(File from) {
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(from), Charsets.UTF_8)
) {
return YamlConfiguration.loadConfiguration(reader);
} catch(IOException e) {
return null;
}
}
/**
* Import region specific settings from a RegionForSale source to an AreaShop target ConfigurationSection.
* @param from RegionForSale config section that specifies region settings
* @param to AreaShop config section that specifies region settings
* @param region GeneralRegion to copy settings to, or null if doing generic settings
*/
private void importRegionSettings(ConfigurationSection from, ConfigurationSection to, GeneralRegion region) {
// Maximum rental time, TODO check if this is actually the same
if(from.isLong("permissions.max-rent-time")) {
to.set("rent.maxRentTime", minutesToString(from.getLong("permissions.max-rent-time")));
}
// Region rebuild
if(from.getBoolean("region-rebuilding.auto-rebuild")) {
to.set("general.enableRestore", true);
}
// Get price settings
String unit = from.getString("economic-settings.unit-type");
String rentPrice = from.getString("economic-settings.cost-per-unit.rent");
String buyPrice = from.getString("economic-settings.cost-per-unit.buy");
String sellPrice = from.getString("economic-settings.cost-per-unit.selling-price");
// TODO: There is no easy way to import this, setup eventCommandsProfile?
String taxes = from.getString("economic-settings.cost-per-unit.taxes");
// Determine unit and add that to the price
String unitSuffix = "";
if("region".equalsIgnoreCase(unit)) {
// add nothing
} else if("m3".equalsIgnoreCase(unit)) {
unitSuffix = "*%volume%";
} else { // m2 or nothing (in case not set, we should actually look in parent files to correctly set this...)
unitSuffix = "*(%volume%/%height%)"; // This is better than width*depth because of polygon regions
}
// Apply settings
if(rentPrice != null) {
to.set("rent.price", rentPrice + unitSuffix);
}
if(buyPrice != null) {
to.set("buy.price", buyPrice + unitSuffix);
if(sellPrice != null) {
try {
double buyPriceAmount = Double.parseDouble(buyPrice);
double sellPriceAmount = Double.parseDouble(sellPrice);
to.set("buy.moneyBack", sellPriceAmount / buyPriceAmount * 100);
} catch(NumberFormatException e) {
// There is not always a region here for the message, should probably indicate something though
message("import-moneyBackFailed", buyPrice, sellPrice);
}
}
}
// Set rented until
if(from.isLong("info.last-withdrawal")
&& region != null
&& region instanceof RentRegion) {
RentRegion rentRegion = (RentRegion)region;
long lastWithdrawal = from.getLong("info.last-withdrawal");
// Because the rental duration is already imported into the region and its parents this should be correct
rentRegion.setRentedUntil(lastWithdrawal + rentRegion.getDuration());
}
// Import signs (list of strings like "297, 71, -22")
if(from.isList("info.signs") && region != null) {
for(String signLocation : from.getStringList("info.signs")) {
String[] locationParts = signLocation.split(", ");
if(locationParts.length != 3) {
message("import-invalidSignLocation", region.getName(), signLocation);
continue;
}
// Parse the location
Location location;
try {
location = new Location(region.getWorld(), Double.parseDouble(locationParts[0]), Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]));
} catch(NumberFormatException e) {
message("import-invalidSignLocation", region.getName(), signLocation);
continue;
}
// Check if this location is already added to a region
SignsFeature.RegionSign regionSign = SignsFeature.getSignByLocation(location);
if(regionSign != null) {
if(!regionSign.getRegion().equals(region)) {
message("import-signAlreadyAdded", region.getName(), signLocation, regionSign.getRegion().getName());
}
continue;
}
// SignType and Facing will be written when the sign is updated later
region.getSignsFeature().addSign(location, null, null, null);
}
}
}
private static List<Pair<Integer, String>> timeUnitLookup = new ArrayList<Pair<Integer, String>>() {
{
add(new Pair<>(60 * 24 * 30 * 12, "year"));
add(new Pair<>(60 * 24 * 30, "month"));
add(new Pair<>(60 * 24, "day"));
add(new Pair<>(60, "hour"));
}
};
/**
* Convert minutes to a human-readable string.
* @param minutes Value to convert
* @return String that represents the same length of time in a readable format, like "1 day", "5 minutes", "3 months"
*/
private String minutesToString(long minutes) {
// If the specified number of minutes can map nicely to a higher unit, use that one
String resultUnit = "minute";
long resultValue = minutes;
for(Pair<Integer, String> unit : timeUnitLookup) {
long result = minutes / unit.getKey();
if(resultValue * unit.getKey() == minutes) {
resultUnit = unit.getValue();
resultValue = result;
break;
}
}
return resultValue + " " + resultUnit + (resultValue == 1 ? "" : "s");
}
/**
* Send a message to a target without a prefix.
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
public void messageNoPrefix(String key, Object... replacements) {
plugin.messageNoPrefix(sender, key, replacements);
if(!(sender instanceof ConsoleCommandSender)) {
plugin.messageNoPrefix(Bukkit.getConsoleSender(), key, replacements);
}
}
/**
* Send a message to a target, prefixed by the default chat prefix.
* @param key The key of the language string
* @param replacements The replacements to insert in the message
*/
public void message(String key, Object... replacements) {
plugin.message(sender, key, replacements);
if(!(sender instanceof ConsoleCommandSender)) {
plugin.message(Bukkit.getConsoleSender(), key, replacements);
}
}
}

View File

@ -18,6 +18,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
// TODO fix '/as info' help message hitting replacement limit (improve depth tracking?)
// TODO add info about autoExtend
public class InfoCommand extends CommandAreaShop {
@Override

View File

@ -71,7 +71,7 @@ public class MeCommand extends CommandAreaShop {
// Get the regions the player is added as friend
Set<GeneralRegion> friendRegions = new HashSet<>();
for(GeneralRegion region : plugin.getFileManager().getRegions()) {
if(region.getFriendsFeature().getFriends() != null && region.getFriendsFeature().getFriends().contains(player.getUniqueId())) {
if(region.getFriendsFeature().getFriends().contains(player.getUniqueId())) {
friendRegions.add(region);
}
}

View File

@ -66,9 +66,9 @@ public class SetpriceCommand extends CommandAreaShop {
}
if("default".equalsIgnoreCase(args[1]) || "reset".equalsIgnoreCase(args[1])) {
if(region instanceof RentRegion) {
((RentRegion)region).removePrice();
((RentRegion)region).setPrice(null);
} else if(region instanceof BuyRegion) {
((BuyRegion)region).removePrice();
((BuyRegion)region).setPrice(null);
}
region.update();
plugin.message(sender, "setprice-successRemoved", region);

View File

@ -2,21 +2,21 @@ package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import org.bukkit.entity.Player;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to buy a region.
*/
public class BuyingRegionEvent extends CancellableRegionEvent<BuyRegion> {
private Player player;
private OfflinePlayer player;
/**
* Constructor.
* @param region The region that is about to get bought
* @param player The player that tries to buy the region
*/
public BuyingRegionEvent(BuyRegion region, Player player) {
public BuyingRegionEvent(BuyRegion region, OfflinePlayer player) {
super(region);
this.player = player;
}
@ -25,7 +25,7 @@ public class BuyingRegionEvent extends CancellableRegionEvent<BuyRegion> {
* Get the player that is trying to buy the region.
* @return The player that is trying to buy the region
*/
public Player getPlayer() {
public OfflinePlayer getPlayer() {
return player;
}
}

View File

@ -2,14 +2,14 @@ package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.RentRegion;
import org.bukkit.entity.Player;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to rent a region.
*/
public class RentingRegionEvent extends CancellableRegionEvent<RentRegion> {
private Player player;
private OfflinePlayer player;
private boolean extending;
/**
@ -18,7 +18,7 @@ public class RentingRegionEvent extends CancellableRegionEvent<RentRegion> {
* @param player The player that tries to rent the region
* @param extending true if the player is extending the rental of the region, otherwise false
*/
public RentingRegionEvent(RentRegion region, Player player, boolean extending) {
public RentingRegionEvent(RentRegion region, OfflinePlayer player, boolean extending) {
super(region);
this.player = player;
this.extending = extending;
@ -28,7 +28,7 @@ public class RentingRegionEvent extends CancellableRegionEvent<RentRegion> {
* Get the player that is trying to rent the region.
* @return The player that is trying to rent the region
*/
public Player getPlayer() {
public OfflinePlayer getPlayer() {
return player;
}

View File

@ -2,21 +2,21 @@ package me.wiefferink.areashop.events.ask;
import me.wiefferink.areashop.events.CancellableRegionEvent;
import me.wiefferink.areashop.regions.BuyRegion;
import org.bukkit.entity.Player;
import org.bukkit.OfflinePlayer;
/**
* Broadcasted when a player tries to resell a region.
*/
public class ResellingRegionEvent extends CancellableRegionEvent<BuyRegion> {
private Player player;
private OfflinePlayer player;
/**
* Contructor.
* @param region The region that the player is trying to resell
* @param player The player that is trying to buy this region from the current owner
*/
public ResellingRegionEvent(BuyRegion region, Player player) {
public ResellingRegionEvent(BuyRegion region, OfflinePlayer player) {
super(region);
this.player = player;
}
@ -25,7 +25,7 @@ public class ResellingRegionEvent extends CancellableRegionEvent<BuyRegion> {
* Get the player that is trying to buy the region.
* @return The player that is trying to buy the region
*/
public Player getBuyer() {
public OfflinePlayer getBuyer() {
return player;
}
}

View File

@ -92,7 +92,7 @@ public class FriendsFeature extends RegionFeature {
HashSet<String> result = new HashSet<>();
for(UUID friend : getFriends()) {
OfflinePlayer player = Bukkit.getOfflinePlayer(friend);
if(player != null) {
if(player != null && player.getName() != null) {
result.add(player.getName());
}
}

View File

@ -138,8 +138,8 @@ public class SignsFeature extends RegionFeature {
}
String signPath = "general.signs." + i + ".";
region.setSetting(signPath + "location", Utils.locationToConfig(location));
region.setSetting(signPath + "facing", facing.name());
region.setSetting(signPath + "signType", signType.name());
region.setSetting(signPath + "facing", facing != null ? facing.name() : null);
region.setSetting(signPath + "signType", signType != null ? signType.name() : null);
if(profile != null && profile.length() != 0) {
region.setSetting(signPath + "profile", profile);
}

View File

@ -14,6 +14,7 @@ import me.wiefferink.areashop.commands.GroupdelCommand;
import me.wiefferink.areashop.commands.GroupinfoCommand;
import me.wiefferink.areashop.commands.GrouplistCommand;
import me.wiefferink.areashop.commands.HelpCommand;
import me.wiefferink.areashop.commands.ImportCommand;
import me.wiefferink.areashop.commands.InfoCommand;
import me.wiefferink.areashop.commands.LinksignsCommand;
import me.wiefferink.areashop.commands.MeCommand;
@ -83,6 +84,7 @@ public class CommandManager extends Manager implements CommandExecutor, TabCompl
commands.add(new StackCommand());
commands.add(new SetlandlordCommand());
commands.add(new MessageCommand());
commands.add(new ImportCommand());
// Register commands in bukkit
plugin.getCommand("AreaShop").setExecutor(this);

View File

@ -307,10 +307,20 @@ public class FileManager extends Manager {
public void addRent(RentRegion rent) {
addRentNoSave(rent);
rent.saveRequired();
markGroupsAutoDirty();
}
/**
* Add a buy to the list without saving it to disk (useful for laoding at startup).
* Mark all RegionGroups that they should regenerate regions.
*/
public void markGroupsAutoDirty() {
for(RegionGroup group : getGroups()) {
group.autoDirty();
}
}
/**
* Add a buy to the list without saving it to disk (useful for loading at startup).
* @param buy The buy region to add
*/
public void addBuyNoSave(BuyRegion buy) {
@ -329,6 +339,7 @@ public class FileManager extends Manager {
public void addBuy(BuyRegion buy) {
addBuyNoSave(buy);
buy.saveRequired();
markGroupsAutoDirty();
}
/**

View File

@ -71,7 +71,7 @@ public class BuyRegion extends GeneralRegion {
* @param player Player to check
* @return true if this player owns this region, otherwise false
*/
public boolean isBuyer(Player player) {
public boolean isBuyer(OfflinePlayer player) {
return player != null && isBuyer(player.getUniqueId());
}
@ -161,17 +161,10 @@ public class BuyRegion extends GeneralRegion {
* Change the price of the region.
* @param price The price to set this region to
*/
public void setPrice(double price) {
public void setPrice(Double price) {
setSetting("buy.price", price);
}
/**
* Remove the price so that the price will be taken from a group or the default.yml file
*/
public void removePrice() {
setSetting("buy.price", null);
}
/**
* Set the region into resell mode with the given price.
* @param price The price this region should be put up for sale
@ -260,58 +253,65 @@ public class BuyRegion extends GeneralRegion {
/**
* Buy a region.
* @param player The player that wants to buy the region
* @param offlinePlayer The player that wants to buy the region
* @return true if it succeeded and false if not
*/
@SuppressWarnings("deprecation")
public boolean buy(Player player) {
public boolean buy(OfflinePlayer offlinePlayer) {
// Check if the player has permission
if(player.hasPermission("areashop.buy")) {
if(plugin.hasPermission(offlinePlayer, "areashop.buy")) {
if(plugin.getEconomy() == null) {
message(player, "general-noEconomy");
message(offlinePlayer, "general-noEconomy");
return false;
}
if(isInResellingMode()) {
if(!player.hasPermission("areashop.buyresell")) {
message(player, "buy-noPermissionResell");
if(!plugin.hasPermission(offlinePlayer, "areashop.buyresell")) {
message(offlinePlayer, "buy-noPermissionResell");
return false;
}
} else {
if(!player.hasPermission("areashop.buynormal")) {
message(player, "buy-noPermissionNoResell");
if(!plugin.hasPermission(offlinePlayer, "areashop.buynormal")) {
message(offlinePlayer, "buy-noPermissionNoResell");
return false;
}
}
if(getWorld() == null) {
message(player, "general-noWorld");
message(offlinePlayer, "general-noWorld");
return false;
}
if(getRegion() == null) {
message(player, "general-noRegion");
message(offlinePlayer, "general-noRegion");
return false;
}
if(!isSold() || (isInResellingMode() && !isBuyer(player))) {
if(!isSold() || (isInResellingMode() && !isBuyer(offlinePlayer))) {
boolean isResell = isInResellingMode();
// Check if the players needs to be in the world or region for buying
if(restrictedToRegion() && (!player.getWorld().getName().equals(getWorldName())
|| !getRegion().contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
message(player, "buy-restrictedToRegion");
return false;
}
if(restrictedToWorld() && !player.getWorld().getName().equals(getWorldName())) {
message(player, "buy-restrictedToWorld", player.getWorld().getName());
return false;
// Only relevant if the player is online
Player player = offlinePlayer.getPlayer();
if(player != null) {
// Check if the players needs to be in the region for buying
if(restrictedToRegion() && (!player.getWorld().getName().equals(getWorldName())
|| !getRegion().contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
message(offlinePlayer, "buy-restrictedToRegion");
return false;
}
// Check if the players needs to be in the world for buying
if(restrictedToWorld() && !player.getWorld().getName().equals(getWorldName())) {
message(offlinePlayer, "buy-restrictedToWorld", player.getWorld().getName());
return false;
}
}
// Check region limits
LimitResult limitResult = this.limitsAllow(RegionType.BUY, player);
LimitResult limitResult = this.limitsAllow(RegionType.BUY, offlinePlayer);
AreaShop.debug("LimitResult: " + limitResult.toString());
if(!limitResult.actionAllowed()) {
if(limitResult.getLimitingFactor() == LimitType.TOTAL) {
message(player, "total-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
message(offlinePlayer, "total-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
if(limitResult.getLimitingFactor() == LimitType.BUYS) {
message(player, "buy-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
message(offlinePlayer, "buy-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
// Should not be reached, but is safe like this
@ -319,24 +319,24 @@ public class BuyRegion extends GeneralRegion {
}
// Check if the player has enough money
if((!isResell && plugin.getEconomy().has(player, getWorldName(), getPrice())) || (isResell && plugin.getEconomy().has(player, getWorldName(), getResellPrice()))) {
if((!isResell && plugin.getEconomy().has(offlinePlayer, getWorldName(), getPrice())) || (isResell && plugin.getEconomy().has(offlinePlayer, getWorldName(), getResellPrice()))) {
UUID oldOwner = getBuyer();
if(isResell && oldOwner != null) {
// Broadcast and check event
ResellingRegionEvent event = new ResellingRegionEvent(this, player);
ResellingRegionEvent event = new ResellingRegionEvent(this, offlinePlayer);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(player, "general-cancelled", event.getReason());
message(offlinePlayer, "general-cancelled", event.getReason());
return false;
}
getFriendsFeature().clearFriends();
double resellPrice = getResellPrice();
// Transfer the money to the previous owner
EconomyResponse r = plugin.getEconomy().withdrawPlayer(player, getWorldName(), getResellPrice());
EconomyResponse r = plugin.getEconomy().withdrawPlayer(offlinePlayer, getWorldName(), getResellPrice());
if(!r.transactionSuccess()) {
message(player, "buy-payError");
AreaShop.debug("Something went wrong with getting money from " + player.getName() + " while buying " + getName() + ": " + r.errorMessage);
message(offlinePlayer, "buy-payError");
AreaShop.debug("Something went wrong with getting money from " + offlinePlayer.getName() + " while buying " + getName() + ": " + r.errorMessage);
return false;
}
r = null;
@ -349,14 +349,14 @@ public class BuyRegion extends GeneralRegion {
r = plugin.getEconomy().depositPlayer(oldOwnerName, getWorldName(), getResellPrice());
}
if(r == null || !r.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying '" + oldOwnerName + "' " + getFormattedPrice() + " for his resell of region " + getName() + " to " + player.getName());
AreaShop.warn("Something went wrong with paying '" + oldOwnerName + "' " + getFormattedPrice() + " for his resell of region " + getName() + " to " + offlinePlayer.getName());
}
// Resell is done, disable that now
disableReselling();
// Run commands
this.runEventCommands(RegionEvent.RESELL, true);
// Set the owner
setBuyer(player.getUniqueId());
setBuyer(offlinePlayer.getUniqueId());
updateLastActiveTime();
// Update everything
@ -366,7 +366,7 @@ public class BuyRegion extends GeneralRegion {
this.notifyAndUpdate(new ResoldRegionEvent(this, oldOwner));
// Send message to the player
message(player, "buy-successResale", oldOwnerName);
message(offlinePlayer, "buy-successResale", oldOwnerName);
Player seller = Bukkit.getPlayer(oldOwner);
if(seller != null) {
message(seller, "buy-successSeller", resellPrice);
@ -375,17 +375,17 @@ public class BuyRegion extends GeneralRegion {
this.runEventCommands(RegionEvent.RESELL, false);
} else {
// Broadcast and check event
BuyingRegionEvent event = new BuyingRegionEvent(this, player);
BuyingRegionEvent event = new BuyingRegionEvent(this, offlinePlayer);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(player, "general-cancelled", event.getReason());
message(offlinePlayer, "general-cancelled", event.getReason());
return false;
}
// Substract the money from the players balance
EconomyResponse r = plugin.getEconomy().withdrawPlayer(player, getWorldName(), getPrice());
EconomyResponse r = plugin.getEconomy().withdrawPlayer(offlinePlayer, getWorldName(), getPrice());
if(!r.transactionSuccess()) {
message(player, "buy-payError");
message(offlinePlayer, "buy-payError");
return false;
}
// Optionally give money to the landlord
@ -401,14 +401,14 @@ public class BuyRegion extends GeneralRegion {
r = plugin.getEconomy().depositPlayer(landlordName, getWorldName(), getPrice());
}
if(r != null && !r.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying '" + landlordName + "' " + getFormattedPrice() + " for his sell of region " + getName() + " to " + player.getName());
AreaShop.warn("Something went wrong with paying '" + landlordName + "' " + getFormattedPrice() + " for his sell of region " + getName() + " to " + offlinePlayer.getName());
}
}
// Run commands
this.runEventCommands(RegionEvent.BOUGHT, true);
// Set the owner
setBuyer(player.getUniqueId());
setBuyer(offlinePlayer.getUniqueId());
updateLastActiveTime();
// Notify about updates
@ -418,7 +418,7 @@ public class BuyRegion extends GeneralRegion {
handleSchematicEvent(RegionEvent.BOUGHT);
// Send message to the player
message(player, "buy-succes");
message(offlinePlayer, "buy-succes");
// Run commands
this.runEventCommands(RegionEvent.BOUGHT, false);
}
@ -426,20 +426,20 @@ public class BuyRegion extends GeneralRegion {
} else {
// Player has not enough money
if(isResell) {
message(player, "buy-lowMoneyResell", Utils.formatCurrency(plugin.getEconomy().getBalance(player, getWorldName())));
message(offlinePlayer, "buy-lowMoneyResell", Utils.formatCurrency(plugin.getEconomy().getBalance(offlinePlayer, getWorldName())));
} else {
message(player, "buy-lowMoney", Utils.formatCurrency(plugin.getEconomy().getBalance(player, getWorldName())));
message(offlinePlayer, "buy-lowMoney", Utils.formatCurrency(plugin.getEconomy().getBalance(offlinePlayer, getWorldName())));
}
}
} else {
if(isBuyer(player)) {
message(player, "buy-yours");
if(isBuyer(offlinePlayer)) {
message(offlinePlayer, "buy-yours");
} else {
message(player, "buy-someoneElse");
message(offlinePlayer, "buy-someoneElse");
}
}
} else {
message(player, "buy-noPermission");
message(offlinePlayer, "buy-noPermission");
}
return false;
}

View File

@ -413,6 +413,18 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
}
}
/**
* Change the owner of the region.
* @param player The player that should be the owner
*/
public void setOwner(UUID player) {
if(this instanceof RentRegion) {
((RentRegion)this).setRenter(player);
} else {
((BuyRegion)this).setBuyer(player);
}
}
/**
* Get the landlord of this region (the player that receives any revenue from this region).
* @return The UUID of the landlord of this region
@ -1137,19 +1149,19 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
* @param player The player to check it for
* @return LimitResult containing if it is allowed, why and limiting factor
*/
public LimitResult limitsAllow(RegionType type, Player player) {
public LimitResult limitsAllow(RegionType type, OfflinePlayer player) {
return limitsAllow(type, player, false);
}
/**
* Check if the player can buy/rent this region, detailed info in the result object.
* @param type The type of region to check
* @param player The player to check it for
* @param offlinePlayer The player to check it for
* @param extend Check for extending of rental regions
* @return LimitResult containing if it is allowed, why and limiting factor
*/
public LimitResult limitsAllow(RegionType type, Player player, boolean extend) {
if(player.hasPermission("areashop.limitbypass")) {
public LimitResult limitsAllow(RegionType type, OfflinePlayer offlinePlayer, boolean extend) {
if(plugin.hasPermission(offlinePlayer, "areashop.limitbypass")) {
return new LimitResult(true, null, 0, 0, null);
}
GeneralRegion exclude = null;
@ -1166,7 +1178,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
List<String> groups = new ArrayList<>(plugin.getConfig().getConfigurationSection("limitGroups").getKeys(false));
while(!groups.isEmpty()) {
String group = groups.get(0);
if(player.hasPermission("areashop.limits." + group) && this.matchesLimitGroup(group)) {
if(plugin.hasPermission(offlinePlayer, "areashop.limits." + group) && this.matchesLimitGroup(group)) {
String pathPrefix = "limitGroups." + group + ".";
if(!plugin.getConfig().isInt(pathPrefix + "total")) {
AreaShop.warn("Limit group " + group + " in the config.yml file does not correctly specify the number of total regions (should be specified as total: <number>)");
@ -1177,12 +1189,12 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
int totalLimit = plugin.getConfig().getInt("limitGroups." + group + ".total");
int typeLimit = plugin.getConfig().getInt("limitGroups." + group + "." + typePath);
//AreaShop.debug("typeLimitOther="+typeLimit+", typePath="+typePath);
int totalCurrent = hasRegionsInLimitGroup(player, group, plugin.getFileManager().getRegions(), exclude);
int totalCurrent = hasRegionsInLimitGroup(offlinePlayer, group, plugin.getFileManager().getRegions(), exclude);
int typeCurrent;
if(type == RegionType.RENT) {
typeCurrent = hasRegionsInLimitGroup(player, group, plugin.getFileManager().getRents(), exclude);
typeCurrent = hasRegionsInLimitGroup(offlinePlayer, group, plugin.getFileManager().getRents(), exclude);
} else {
typeCurrent = hasRegionsInLimitGroup(player, group, plugin.getFileManager().getBuys(), exclude);
typeCurrent = hasRegionsInLimitGroup(offlinePlayer, group, plugin.getFileManager().getBuys(), exclude);
}
if(totalLimit == -1) {
totalLimit = Integer.MAX_VALUE;
@ -1196,7 +1208,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
// Get the highest number from the groups of the same category
List<String> groupsCopy = new ArrayList<>(groups);
for(String checkGroup : groupsCopy) {
if(player.hasPermission("areashop.limits." + checkGroup) && this.matchesLimitGroup(checkGroup)) {
if(plugin.hasPermission(offlinePlayer, "areashop.limits." + checkGroup) && this.matchesLimitGroup(checkGroup)) {
if(limitGroupsOfSameCategory(group, checkGroup)) {
groups.remove(checkGroup);
int totalLimitOther = plugin.getConfig().getInt("limitGroups." + checkGroup + ".total");
@ -1338,7 +1350,7 @@ public abstract class GeneralRegion implements GeneralRegionInterface, Comparabl
* @param exclude Exclude this region from the count
* @return The number of regions that the player has bought or rented matching the limit group (worlds and groups filters)
*/
public int hasRegionsInLimitGroup(Player player, String limitGroup, List<? extends GeneralRegion> regions, GeneralRegion exclude) {
public int hasRegionsInLimitGroup(OfflinePlayer player, String limitGroup, List<? extends GeneralRegion> regions, GeneralRegion exclude) {
int result = 0;
for(GeneralRegion region : regions) {
if(region.isOwner(player) && region.matchesLimitGroup(limitGroup) && (exclude == null || !exclude.getName().equals(region.getName()))) {

View File

@ -2,10 +2,10 @@ package me.wiefferink.areashop.regions;
import me.wiefferink.areashop.AreaShop;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
// TODO consider switching to saving lowercase regions
@ -13,35 +13,86 @@ public class RegionGroup {
private AreaShop plugin;
private String name;
private Set<String> regions;
private Set<String> autoRegions;
private boolean autoDirty;
private Set<String> worlds;
/**
* Constructor, used when creating new groups or restoring them from groups.yml at server boot
* Constructor, used when creating new groups or restoring them from groups.yml at server boot.
* @param plugin The AreaShop plugin
* @param name Name of the group, has to be unique
*/
public RegionGroup(AreaShop plugin, String name) {
this.plugin = plugin;
this.name = name;
this.autoDirty = true;
setSetting("name", name);
// Delete duplicates
List<String> members = getMembers();
int previousCount = members.size();
List<String> newMembers = new ArrayList<>();
while(!members.isEmpty()) {
String member = members.remove(0);
// If the region has been deleted also clean it from the group
if(plugin.getFileManager().getRegion(member) != null) {
newMembers.add(member);
}
while(members.contains(member)) {
members.remove(member);
// Load regions and worlds
regions = new HashSet<>(getSettings().getStringList("regions"));
worlds = new HashSet<>(getSettings().getStringList("worlds"));
}
/**
* Get automatically added regions.
*/
public Set<String> getAutoRegions() {
if(autoDirty) {
autoRegions = new HashSet<>();
for(GeneralRegion region : plugin.getFileManager().getRegions()) {
if(worlds.contains(region.getWorldName())) {
autoRegions.add(region.getName());
}
}
autoDirty = false;
}
if(newMembers.size() != previousCount) {
setSetting("regions", newMembers);
AreaShop.debug("group save required because of changed member size", newMembers);
return autoRegions;
}
/**
* Mark that automatically added regions should be regenerated.
*/
public void autoDirty() {
autoDirty = true;
}
/**
* Adds a world from which all regions should be added to the group.
* @param world World from which all regions should be added
* @return true if the region was not already added, otherwise false
*/
public boolean addWorld(String world) {
if(worlds.add(world)) {
setSetting("regionsFromWorlds", new ArrayList<>(worlds));
saveRequired();
autoDirty();
return true;
}
return false;
}
/**
* Remove a member from the group.
* @param world World to remove
* @return true if the region was in the group before, otherwise false
*/
public boolean removeWorld(String world) {
if(worlds.remove(world)) {
setSetting("regionsFromWorlds", new ArrayList<>(worlds));
saveRequired();
autoDirty();
return true;
}
return false;
}
/**
* Get all worlds from which regions are added automatically.
* @return A list with the names of all worlds (immutable)
*/
public Set<String> getWorlds() {
return new HashSet<>(worlds);
}
/**
@ -50,15 +101,12 @@ public class RegionGroup {
* @return true if the region was not already added, otherwise false
*/
public boolean addMember(GeneralRegion region) {
List<String> members = getMembers();
if(members.contains(region.getName())) {
return false;
} else {
members.add(region.getName());
setSetting("regions", members);
this.saveRequired();
if(regions.add(region.getName())) {
setSetting("regions", new ArrayList<>(regions));
saveRequired();
return true;
}
return false;
}
/**
@ -67,26 +115,30 @@ public class RegionGroup {
* @return true if the region was in the group before, otherwise false
*/
public boolean removeMember(GeneralRegion region) {
if(!isMember(region)) {
return false;
if(regions.remove(region.getName())) {
setSetting("regions", new ArrayList<>(regions));
saveRequired();
return true;
}
List<String> members = getMembers();
members.remove(region.getName());
setSetting("regions", members);
this.saveRequired();
return true;
return false;
}
/**
* Get all members of the group.
* @return A list with the names of all members of the group
* @return A list with the names of all members of the group (immutable)
*/
public List<String> getMembers() {
if(getSettings() == null || getSettings().getStringList("regions") == null) {
return new ArrayList<>();
}
return getSettings().getStringList("regions");
public Set<String> getMembers() {
HashSet<String> result = new HashSet<>(regions);
result.addAll(getAutoRegions());
return result;
}
/**
* Get all manually added members of the group.
* @return A list with the names of all members of the group (immutable)
*/
public Set<String> getManualMembers() {
return new HashSet<>(regions);
}
/**
@ -139,7 +191,12 @@ public class RegionGroup {
* @return The ConfigurationSection with the settings of the group
*/
public ConfigurationSection getSettings() {
return plugin.getFileManager().getGroupSettings(name);
ConfigurationSection result = plugin.getFileManager().getGroupSettings(name);
if(result != null) {
return result;
} else {
return new YamlConfiguration();
}
}
/**

View File

@ -158,7 +158,7 @@ public class RentRegion extends GeneralRegion {
case AreaShop.tagRawMoneyBackAmount:
return getMoneyBackAmount();
case AreaShop.tagMoneyBackPercentage:
return getMoneyBackPercentage() % 1.0 == 0.0 ? (int)getMoneyBackPercentage() : getMoneyBackPercentage();
return (getMoneyBackPercentage() % 1.0) == 0.0 ? (int)getMoneyBackPercentage() : getMoneyBackPercentage();
case AreaShop.tagTimesExtended:
return this.getTimesExtended();
case AreaShop.tagMaxExtends:
@ -290,17 +290,10 @@ public class RentRegion extends GeneralRegion {
* Change the price of the region.
* @param price The price of the region
*/
public void setPrice(double price) {
public void setPrice(Double price) {
setSetting("rent.price", price);
}
/**
* Remove the price so that the price will be taken from a group or the default.yml file
*/
public void removePrice() {
setSetting("rent.price", null);
}
/**
* Set the duration of the rent.
* @param duration The duration of the rent (as specified on the documentation pages)
@ -353,6 +346,13 @@ public class RentRegion extends GeneralRegion {
public boolean checkExpiration() {
long now = Calendar.getInstance().getTimeInMillis();
if(!isDeleted() && isRented() && now > getRentedUntil()) {
// Extend rent if configured for that
if(getBooleanSetting("rent.autoExtend")) {
if(extend()) {
return false;
}
}
// Send message to the player if online
Player player = Bukkit.getPlayer(getRenter());
if(unRent(false, null)) {
@ -411,64 +411,83 @@ public class RentRegion extends GeneralRegion {
warningsDoneUntil = sendUntil;
}
/**
* Try to extend the rent for the current owner, respecting all restrictions.
* @return true if successful, otherwise false
*/
public boolean extend() {
if(!isRented()) {
return false;
}
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(getRenter());
return offlinePlayer != null && rent(offlinePlayer);
}
/**
* Rent a region.
* @param player The player that wants to rent the region
* @param offlinePlayer The player that wants to rent the region
* @return true if it succeeded and false if not
*/
@SuppressWarnings("deprecation")
public boolean rent(Player player) {
public boolean rent(OfflinePlayer offlinePlayer) {
if(plugin.getEconomy() == null) {
message(player, "general-noEconomy");
message(offlinePlayer, "general-noEconomy");
return false;
}
//Check if the player has permission
if(player.hasPermission("areashop.rent")) {
if(plugin.hasPermission(offlinePlayer, "areashop.rent")) {
if(getWorld() == null) {
message(player, "general-noWorld");
message(offlinePlayer, "general-noWorld");
return false;
}
if(getRegion() == null) {
message(player, "general-noRegion");
message(offlinePlayer, "general-noRegion");
return false;
}
boolean extend = false;
if(getRenter() != null && player.getUniqueId().equals(getRenter())) {
if(getRenter() != null && offlinePlayer.getUniqueId().equals(getRenter())) {
extend = true;
}
// Check if the region is available for renting or if the player wants to extend the rent
if(!isRented() || extend) {
// Check if the players needs to be in the world or region for buying
if(restrictedToRegion() && (!player.getWorld().getName().equals(getWorldName())
|| !getRegion().contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
message(player, "rent-restrictedToRegion");
return false;
}
if(restrictedToWorld() && !player.getWorld().getName().equals(getWorldName())) {
message(player, "rent-restrictedToWorld", player.getWorld().getName());
return false;
// These checks are only relevant for online players doing the renting/buying themselves
Player player = offlinePlayer.getPlayer();
if(player != null) {
// Check if the players needs to be in the region for renting
if(restrictedToRegion() && (!player.getWorld().getName().equals(getWorldName())
|| !getRegion().contains(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()))) {
message(offlinePlayer, "rent-restrictedToRegion");
return false;
}
// Check if the players needs to be in the world for renting
if(restrictedToWorld() && !player.getWorld().getName().equals(getWorldName())) {
message(offlinePlayer, "rent-restrictedToWorld", player.getWorld().getName());
return false;
}
}
// Check region limits if this is not extending
if(!(extend && config.getBoolean("allowRegionExtendsWhenAboveLimits"))) {
LimitResult limitResult;
if(extend) {
limitResult = this.limitsAllow(RegionType.RENT, player, true);
limitResult = this.limitsAllow(RegionType.RENT, offlinePlayer, true);
} else {
limitResult = this.limitsAllow(RegionType.RENT, player);
limitResult = this.limitsAllow(RegionType.RENT, offlinePlayer);
}
AreaShop.debug("LimitResult: " + limitResult.toString());
if(!limitResult.actionAllowed()) {
if(limitResult.getLimitingFactor() == LimitType.TOTAL) {
message(player, "total-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
message(offlinePlayer, "total-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
if(limitResult.getLimitingFactor() == LimitType.RENTS) {
message(player, "rent-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
message(offlinePlayer, "rent-maximum", limitResult.getMaximum(), limitResult.getCurrent(), limitResult.getLimitingGroup());
return false;
}
if(limitResult.getLimitingFactor() == LimitType.EXTEND) {
message(player, "rent-maximumExtend", limitResult.getMaximum(), limitResult.getCurrent() + 1, limitResult.getLimitingGroup());
message(offlinePlayer, "rent-maximumExtend", limitResult.getMaximum(), limitResult.getCurrent() + 1, limitResult.getLimitingGroup());
return false;
}
return false;
@ -476,9 +495,9 @@ public class RentRegion extends GeneralRegion {
}
// Check if the player can still extend this rent
if(extend && !player.hasPermission("areashop.rentextendbypass")) {
if(extend && !plugin.hasPermission(offlinePlayer, "areashop.rentextendbypass")) {
if(getMaxExtends() >= 0 && getTimesExtended() >= getMaxExtends()) {
message(player, "rent-maxExtends");
message(offlinePlayer, "rent-maxExtends");
return false;
}
}
@ -493,12 +512,12 @@ public class RentRegion extends GeneralRegion {
timeRented = getRentedUntil() - timeNow;
}
if((timeRented + getDuration()) > (maxRentTime)
&& !player.hasPermission("areashop.renttimebypass")
&& !plugin.hasPermission(offlinePlayer, "areashop.renttimebypass")
&& maxRentTime != -1) {
// Extend to the maximum instead of adding a full period
if(getBooleanSetting("rent.extendToFullWhenAboveMaxRentTime")) {
if(timeRented >= maxRentTime) {
message(player, "rent-alreadyAtFull");
message(offlinePlayer, "rent-alreadyAtFull");
return false;
} else {
long toRentPart = maxRentTime - timeRented;
@ -506,25 +525,25 @@ public class RentRegion extends GeneralRegion {
price = ((double)toRentPart) / getDuration() * price;
}
} else {
message(player, "rent-maxRentTime");
message(offlinePlayer, "rent-maxRentTime");
return false;
}
}
if(plugin.getEconomy().has(player, getWorldName(), price)) {
if(plugin.getEconomy().has(offlinePlayer, getWorldName(), price)) {
// Broadcast and check event
RentingRegionEvent event = new RentingRegionEvent(this, player, extend);
RentingRegionEvent event = new RentingRegionEvent(this, offlinePlayer, extend);
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) {
message(player, "general-cancelled", event.getReason());
message(offlinePlayer, "general-cancelled", event.getReason());
return false;
}
// Substract the money from the players balance
EconomyResponse r = plugin.getEconomy().withdrawPlayer(player, getWorldName(), price);
EconomyResponse r = plugin.getEconomy().withdrawPlayer(offlinePlayer, getWorldName(), price);
if(!r.transactionSuccess()) {
message(player, "rent-payError");
AreaShop.debug("Something went wrong with getting money from " + player.getName() + " while renting " + getName() + ": " + r.errorMessage);
message(offlinePlayer, "rent-payError");
AreaShop.debug("Something went wrong with getting money from " + offlinePlayer.getName() + " while renting " + getName() + ": " + r.errorMessage);
return false;
}
// Optionally give money to the landlord
@ -540,7 +559,7 @@ public class RentRegion extends GeneralRegion {
r = plugin.getEconomy().depositPlayer(landlordName, getWorldName(), price);
}
if(r == null || !r.transactionSuccess()) {
AreaShop.warn("Something went wrong with paying '" + landlordName + "' " + Utils.formatCurrency(price) + " for his rent of region " + getName() + " to " + player.getName());
AreaShop.warn("Something went wrong with paying '" + landlordName + "' " + Utils.formatCurrency(price) + " for his rent of region " + getName() + " to " + offlinePlayer.getName());
}
}
@ -564,7 +583,7 @@ public class RentRegion extends GeneralRegion {
// Add values to the rent and send it to FileManager
setRentedUntil(calendar.getTimeInMillis());
setRenter(player.getUniqueId());
setRenter(offlinePlayer.getUniqueId());
updateLastActiveTime();
// Fire schematic event and updated times extended
@ -580,11 +599,11 @@ public class RentRegion extends GeneralRegion {
// Send message to the player
if(extendToMax) {
message(player, "rent-extendedToMax");
message(offlinePlayer, "rent-extendedToMax");
} else if(extend) {
message(player, "rent-extended");
message(offlinePlayer, "rent-extended");
} else {
message(player, "rent-rented");
message(offlinePlayer, "rent-rented");
}
if(!extend) {
// Run commands
@ -597,16 +616,16 @@ public class RentRegion extends GeneralRegion {
} else {
// Player has not enough money
if(extend) {
message(player, "rent-lowMoneyExtend", Utils.formatCurrency(plugin.getEconomy().getBalance(player, getWorldName())));
message(offlinePlayer, "rent-lowMoneyExtend", Utils.formatCurrency(plugin.getEconomy().getBalance(offlinePlayer, getWorldName())));
} else {
message(player, "rent-lowMoneyRent", Utils.formatCurrency(plugin.getEconomy().getBalance(player, getWorldName())));
message(offlinePlayer, "rent-lowMoneyRent", Utils.formatCurrency(plugin.getEconomy().getBalance(offlinePlayer, getWorldName())));
}
}
} else {
message(player, "rent-someoneElse");
message(offlinePlayer, "rent-someoneElse");
}
} else {
message(player, "rent-noPermission");
message(offlinePlayer, "rent-noPermission");
}
return false;
}

View File

@ -104,6 +104,8 @@ public class Analytics {
}
});
// TODO track rent/buy/unrent/sell/resell actions (so that it can be reported per collection interval)
AreaShop.debug("Started bstats.org statistics service");
} catch(Exception e) {
AreaShop.debug("Could not start bstats.org statistics service");

View File

@ -108,6 +108,7 @@ general:
flagProfile:
ALL: # Flags that should always be applied
priority: 10
owners: ''
members: ''
interact: 'deny g:non_members' # Only allow region members/owners to use things in the region (chests, furnace, animals, etc.)
use: 'deny g:non_members'
@ -196,6 +197,8 @@ rent:
price: 1000
# The default duration of a rental region, you can find all time indicators in config.yml below the RENTING header.
duration: '1 day'
# Automatically extend the rental when it is running out (if the player meets the criteria)
autoExtend: false
# The percentage of the rent price you get back if you unrent the region (only the time that is unused is given back).
# (variables, mathematical expressions and JavaScript can be used)
moneyBack: 100

View File

@ -98,6 +98,7 @@ help-delFriend: "%lang:helpCommand|/as delfriend|% Delete a friend from a region
help-linksigns: "%lang:helpCommand|/as linksigns|% Use bulk sign linking mode."
help-stack: "%lang:helpCommand|/as stack|% Create multiple regions and add them."
help-setlandlord: "%lang:helpCommand|/as setlandlord|% Set the landlord of a region."
help-import: "%lang:helpCommand|/as import|% Import region from RegionForSale.%"
rent-help: "/as rent [region], the region you stand in will be used if not specified."
rent-noPermission: "You don't have permission to rent a region."
@ -538,6 +539,30 @@ message-help: "/as message <player> <message...>"
message-noPermission: "You do not have permission to send AreaShop messages."
message-notOnline: "%0% is not online."
import-help: "/as import RegionForSale"
import-wrongSource: "Specify from where you want to import data, options: RegionForSale."
import-noPluginFolder: "Could not find the %0% folder, are you sure you are importing from the correct source?"
import-noWorldsFolder: "There is no 'plugins/RegionForSale/worlds' folder, therefore nothing could be imported."
import-start: "Starting import of RegionForSale data:"
import-doWorld: "[darkgreen][bold]► World %0%:"
import-noBukkitWorld: "[red]Could not find world in Bukkit."
import-noRegionManager: "[red]Could not get RegionManager from WorldGuard, are regions enabled in this world?"
import-noRegions: "[red]No regions.yml file found."
import-loadRegionsFailed: "[red]Could not load regions.yml at: %0%"
import-invalidRegionSection: "[red]Invalid section in regions.yml: %0%."
import-noWorldGuardRegion: "[red]WorldGuard region not found: %0%."
import-noWorldGuardRegionParent: "[red]WorldGuard region for parent region not found: %0%."
import-buyAndRent: "[gold]%0% can be bought and rented, only adding as buy in AreaShop (cannot be both)."
import-noBuyAndNoRent: "[red]%0% cannot be bought and cannot be rented, skipping import."
import-imported: "[green]%lang:tRegion|%0%|% imported."
import-loadWorldConfigFailed: "[red]Loading world-specific config.yml failed: %0%."
import-improperParentRegion: "[red]Parent region %0% does not have readable settings."
import-alreadyAdded: "[red]Region '%0%' has already been added."
import-alreadyAddedOtherWorld: "[red]Region '%0%' has already been added in world %1%, regions in different worlds cannot have the same name in AreaShop."
import-invalidSignLocation: "[red]Failed to import a sign of region '%0%', invalid location: '%1%'."
import-signAlreadyAdded: "[red]Skipped adding sign at location '%1%' to region '%0%', it is already added to region '%2%'."
import-moneyBackFailed: "[red]Failed to parse buy price '%0%' and sell price '%1%' to set the money back percentage."
confirm-rent:
- "%lang:prefix%Are you sure you want to rent %lang:tRegion|%0%|%?[break]"
- "[darkgreen][bold]►[reset] %lang:button|Click to rent %0%|%"

View File

@ -57,6 +57,7 @@ permissions:
areashop.setlandlord: true
areashop.linksigns: true
areashop.message: true
areashop.import: true
areashop.landlord.*:
description: Give access to all landlord features
children:
@ -263,4 +264,7 @@ permissions:
default: op
areashop.message:
description: Allows you to send AreaShop messages to players
default: op
default: op
areashop.import:
description: Allows importing regions from other plugins
default: op

View File

@ -1,6 +1,11 @@
package me.wiefferink.areashop.interfaces;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -36,9 +41,25 @@ public class RegionAccessSet {
/**
* Get the groups.
* @return Set with groups added to this RegionAccessSet
* @return Set with groups added to this RegionAccessSet.
*/
public Set<String> getGroupNames() {
return groupNames;
}
/**
* Get this access set as a list of player UUIDs.
* @return List of player UUIDs, first players already added by UUID, then players added by name, groups are not in the list
*/
public List<UUID> asUniqueIdList() {
List<UUID> result = new ArrayList<>();
result.addAll(playerUniqueIds);
for(String playerName : playerNames) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
if(offlinePlayer != null && offlinePlayer.getUniqueId() != null) {
result.add(offlinePlayer.getUniqueId());
}
}
return result;
}
}

View File

@ -28,14 +28,14 @@ public abstract class WorldGuardInterface {
/**
* Parse an owner(s) string and set the players as owner of the WorldGuard region (set by UUID or name depending on implementation).
* @param region The WorldGuard region to set the owners of
* @param regionAccessSet The owner(s) string to set
* @param regionAccessSet The owner(s) to set
*/
public abstract void setOwners(ProtectedRegion region, RegionAccessSet regionAccessSet);
/**
* Parse a member(s) string and set the players as member of the WorldGuard region (set by UUID or name depending on implementation).
* @param region The WorldGuard region to set the members of
* @param regionAccessSet The member(s) string to set
* @param regionAccessSet The member(s) to set
*/
public abstract void setMembers(ProtectedRegion region, RegionAccessSet regionAccessSet);
@ -55,6 +55,20 @@ public abstract class WorldGuardInterface {
*/
public abstract boolean containsOwner(ProtectedRegion region, UUID player);
/**
* Get the members of a region.
* @param region to get the members of
* @return RegionAccessSet with all members (by uuid and name) and groups of the given region
*/
public abstract RegionAccessSet getMembers(ProtectedRegion region);
/**
* Get the owners of a region.
* @param region to get the owners of
* @return RegionAccessSet with all owners (by uuid and name) and groups of the given region
*/
public abstract RegionAccessSet getOwners(ProtectedRegion region);
// New flag system was introcuded in version 6.1.3, requiring different flag parsing
/**
* Get a flag from the name of a flag.
@ -78,4 +92,4 @@ public abstract class WorldGuardInterface {
* @return The RegionGroup denoted by the input
*/
public abstract RegionGroup parseFlagGroupInput(RegionGroupFlag flag, String input) throws InvalidFlagFormat;
}
}

View File

@ -21,6 +21,7 @@ import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.world.registry.WorldData;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionType;
import me.wiefferink.areashop.interfaces.AreaShopInterface;
import me.wiefferink.areashop.interfaces.GeneralRegionInterface;
import me.wiefferink.areashop.interfaces.WorldEditInterface;
@ -80,19 +81,22 @@ public class WorldEditHandler6 extends WorldEditInterface {
BlockTransformExtent extent = new BlockTransformExtent(clipboardHolder.getClipboard(), clipboardHolder.getTransform(), editSession.getWorld().getWorldData().getBlockRegistry());
ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), editSession, origin);
copy.setTransform(clipboardHolder.getTransform());
// Mask to region (for polygon regions)
copy.setSourceMask(new Mask() {
@Override
public boolean test(Vector vector) {
return region.contains(vector);
}
// Mask to region (for polygon and other weird shaped regions)
// TODO make this more efficient (especially for polygon regions)
if(region.getType() != RegionType.CUBOID) {
copy.setSourceMask(new Mask() {
@Override
public boolean test(Vector vector) {
return region.contains(vector);
}
@Nullable
@Override
public Mask2D toMask2D() {
return null;
}
});
@Nullable
@Override
public Mask2D toMask2D() {
return null;
}
});
}
Operations.completeLegacy(copy);
} catch(MaxChangedBlocksException e) {
pluginInterface.getLogger().warning("Exeeded the block limit while restoring schematic of " + regionInterface.getName() + ", limit in exception: " + e.getBlockLimit() + ", limit passed by AreaShop: " + pluginInterface.getConfig().getInt("maximumBlocks"));

View File

@ -34,4 +34,4 @@
<type>jar</type>
</dependency>
</dependencies>
</project>
</project>

View File

@ -54,6 +54,22 @@ public class WorldGuardHandler5 extends WorldGuardInterface {
}
}
@Override
public RegionAccessSet getMembers(ProtectedRegion region) {
RegionAccessSet result = new RegionAccessSet();
result.getGroupNames().addAll(region.getMembers().getGroups());
result.getPlayerNames().addAll(region.getMembers().getPlayers());
return result;
}
@Override
public RegionAccessSet getOwners(ProtectedRegion region) {
RegionAccessSet result = new RegionAccessSet();
result.getGroupNames().addAll(region.getOwners().getGroups());
result.getPlayerNames().addAll(region.getOwners().getPlayers());
return result;
}
/**
* Build a DefaultDomain from a RegionAccessSet.
* @param regionAccessSet RegionAccessSet to read

View File

@ -52,6 +52,24 @@ public class WorldGuardHandler6 extends WorldGuardInterface {
}
}
@Override
public RegionAccessSet getMembers(ProtectedRegion region) {
RegionAccessSet result = new RegionAccessSet();
result.getGroupNames().addAll(region.getMembers().getGroups());
result.getPlayerNames().addAll(region.getMembers().getPlayers());
result.getPlayerUniqueIds().addAll(region.getMembers().getUniqueIds());
return result;
}
@Override
public RegionAccessSet getOwners(ProtectedRegion region) {
RegionAccessSet result = new RegionAccessSet();
result.getGroupNames().addAll(region.getOwners().getGroups());
result.getPlayerNames().addAll(region.getOwners().getPlayers());
result.getPlayerUniqueIds().addAll(region.getOwners().getUniqueIds());
return result;
}
/**
* Build a DefaultDomain from a RegionAccessSet.
* @param regionAccessSet RegionAccessSet to read