/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.managed.resourcepacks;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.collections.players.PlayerMap;
import com.jcwhatever.nucleus.events.respacks.MissingRequiredResourcePackEvent;
import com.jcwhatever.nucleus.events.respacks.MissingRequiredResourcePackEvent.Action;
import com.jcwhatever.nucleus.internal.NucLang;
import com.jcwhatever.nucleus.internal.NucMsg;
import com.jcwhatever.nucleus.managed.entity.meta.EntityMeta;
import com.jcwhatever.nucleus.managed.entity.meta.IEntityMetaContext;
import com.jcwhatever.nucleus.managed.language.Localizable;
import com.jcwhatever.nucleus.managed.resourcepacks.IPlayerResourcePacks;
import com.jcwhatever.nucleus.managed.resourcepacks.IResourcePack;
import com.jcwhatever.nucleus.managed.resourcepacks.IResourcePackManager;
import com.jcwhatever.nucleus.managed.resourcepacks.ResourcePackStatus;
import com.jcwhatever.nucleus.managed.scheduler.Scheduler;
import com.jcwhatever.nucleus.managed.teleport.Teleporter;
import com.jcwhatever.nucleus.storage.IDataNode;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.file.FileUtils;
import com.jcwhatever.nucleus.utils.managers.NamedInsensitiveDataManager;
import com.jcwhatever.nucleus.utils.observer.future.FutureResultSubscriber;
import com.jcwhatever.nucleus.utils.observer.future.Result;
import com.jcwhatever.nucleus.utils.validate.IValidator;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
import javax.annotation.Nullable;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* Internal implementation of {@link IResourcePackManager}.
*/
public class InternalResourcePackManager extends NamedInsensitiveDataManager<IResourcePack>
implements IResourcePackManager, Listener {
@Localizable static final String _RESOURCE_PACK_REQUIRED =
"{RED}Server resource pack required to enter {0: world name}.";
private static final String RESOURCE_PACK_KEY = "resource-pack=";
private static final Pattern PATTERN_UNESCAPE = Pattern.compile("\\:", Pattern.LITERAL);
private static final IEntityMetaContext META = EntityMeta.getContext(Nucleus.getPlugin());
private static final String HANDLING_REQUIRED_META_NAME =
InternalResourcePackManager.class.getName() + ":HandleRequired";
private final Map<UUID, PlayerResourcePacks> _playerMap =
new PlayerMap<PlayerResourcePacks>(Nucleus.getPlugin(), 35);
private ResourcePack _defaultPack;
private IDataNode _worldNodes;
/**
* Constructor.
*/
public InternalResourcePackManager(IDataNode dataNode) {
super(dataNode.getNode("packs"), true);
_worldNodes = dataNode.getNode("worlds");
loadDefault();
Bukkit.getPluginManager().registerEvents(this, Nucleus.getPlugin());
}
@Override
@Nullable
public IResourcePack getDefault() {
return _defaultPack;
}
@Nullable
@Override
public IResourcePack getWorld(World world) {
PreCon.notNull(world);
if (!_worldNodes.hasNode(world.getName()))
return null;
IDataNode node = _worldNodes.getNode(world.getName());
String packName = node.getString("pack");
if (packName == null)
return null;
return get(packName);
}
@Override
public void setWorld(World world, @Nullable IResourcePack pack) {
PreCon.notNull(world);
IDataNode node = _worldNodes.getNode(world.getName());
node.set("pack", pack == null ? null : pack.getName());
node.save();
IResourcePack current = getWorld(world);
List<Player> players = world.getPlayers();
for (Player player : players) {
IPlayerResourcePacks packs = get(player);
if (pack != null) {
packs.next(pack, current);
}
else {
packs.remove(current);
}
}
}
@Override
public boolean isRequired(World world) {
PreCon.notNull(world);
if (!_worldNodes.hasNode(world.getName()))
return false;
IDataNode node = _worldNodes.getNode(world.getName());
return node.getBoolean("required");
}
@Override
public void setRequired(World world, boolean isRequired) {
PreCon.notNull(world);
IDataNode node = _worldNodes.getNode(world.getName());
node.set("required", isRequired);
node.save();
}
@Override
public IResourcePack add(String name, String url) {
PreCon.notNullOrEmpty(name);
PreCon.notNull(url);
if (contains(name)) {
return get(name);
}
ResourcePack pack = new ResourcePack(name, url, this);
add(pack);
return pack;
}
@Override
@Nullable
public IResourcePack get(String name) {
PreCon.notNull(name);
if (name.isEmpty())
return _defaultPack;
return super.get(name);
}
@Override
public PlayerResourcePacks get(Player player) {
PreCon.notNull(player);
PlayerResourcePacks packs = _playerMap.get(player.getUniqueId());
if (packs == null) {
packs = new PlayerResourcePacks(player, getDefault());
_playerMap.put(player.getUniqueId(), packs);
}
return packs;
}
@Override
public boolean remove(String name) {
PreCon.notNull(name);
return !name.equalsIgnoreCase("_default") && super.remove(name);
}
@Nullable
@Override
protected IResourcePack load(String name, IDataNode itemNode) {
return new ResourcePack(name, itemNode.getString("url"), this);
}
@Override
protected void save(IResourcePack item, IDataNode itemNode) {
itemNode.set("url", item.getUrl());
}
@EventHandler(priority = EventPriority.MONITOR)
private void onJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
final World world = player.getWorld();
final IResourcePack pack = getWorld(world);
if (pack == null)
return;
pack.apply(player);
if (!isRequired(world) || META.has(player, HANDLING_REQUIRED_META_NAME))
return;
META.set(player, HANDLING_REQUIRED_META_NAME, this);
PlayerResourcePacks packs = get(player);
packs.getFinalStatus().onResult(new FutureResultSubscriber<IPlayerResourcePacks>() {
@Override
public void on(Result<IPlayerResourcePacks> result) {
IPlayerResourcePacks packs = result.getResult();
if (packs != null && packs.getStatus() == ResourcePackStatus.SUCCESS)
return;
Scheduler.runTaskSync(Nucleus.getPlugin(), new Runnable() {
@Override
public void run() {
CharSequence message = NucLang.get(_RESOURCE_PACK_REQUIRED, world.getName()).toString();
MissingRequiredResourcePackEvent event = new MissingRequiredResourcePackEvent(
player, world, pack, new Location(null, 0, 0, 0), Action.KICK, message);
Nucleus.getEventManager().callBukkit(this, event);
if (event.isCancelled())
return;
handleRequiredEvent(event);
}
});
}
});
}
@EventHandler(priority = EventPriority.MONITOR)
private void onChangeWorld(PlayerChangedWorldEvent event) {
final Player player = event.getPlayer();
final World world = player.getWorld();
IPlayerResourcePacks playerPacks = get(player);
final IResourcePack newPack = getWorld(player.getWorld());
IResourcePack fromPack = getWorld(event.getFrom());
if (fromPack != null) {
playerPacks.remove(fromPack);
}
if (newPack != null) {
newPack.apply(player);
if (isRequired(world) && !META.has(player, HANDLING_REQUIRED_META_NAME)) {
META.set(player, HANDLING_REQUIRED_META_NAME, this);
PlayerResourcePacks packs = get(player);
final World prevWorld = event.getFrom();
packs.getFinalStatus().onResult(new FutureResultSubscriber<IPlayerResourcePacks>() {
@Override
public void on(Result<IPlayerResourcePacks> result) {
IPlayerResourcePacks packs = result.getResult();
if (packs != null && packs.getStatus() == ResourcePackStatus.SUCCESS)
return;
CharSequence message = NucLang.get(_RESOURCE_PACK_REQUIRED, world.getName());
MissingRequiredResourcePackEvent event = new MissingRequiredResourcePackEvent(
player, world, newPack, prevWorld.getSpawnLocation(), Action.RELOCATE, message);
Nucleus.getEventManager().callBukkit(this, event);
if (event.isCancelled())
return;
handleRequiredEvent(event);
}
});
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
private void onResourcePackStatus(final PlayerResourcePackStatusEvent event) {
Scheduler.runTaskSync(Nucleus.getPlugin(), new Runnable() {
@Override
public void run() {
handleStatusEvent(event.getPlayer(), event.getStatus());
}
});
}
private void handleStatusEvent(Player player, PlayerResourcePackStatusEvent.Status bukkitStatus) {
PlayerResourcePacks packs = get(player);
if (packs == null)
return;
ResourcePackStatus status = ResourcePackStatus.fromBukkit(bukkitStatus);
packs.setStatus(status);
}
private void handleRequiredEvent(MissingRequiredResourcePackEvent event) {
Player player = event.getPlayer();
CharSequence message = event.getMessage();
switch (event.getAction()) {
case RELOCATE:
Teleporter.teleport(player, event.getRelocation());
// fall through
case IGNORE:
if (message != null) {
NucMsg.tell(player, message);
}
break;
case KICK:
if (message == null) {
message = NucLang.get(_RESOURCE_PACK_REQUIRED, event.getWorld().getName());
}
player.kickPlayer(message.toString());
break;
}
META.set(player, HANDLING_REQUIRED_META_NAME, null);
}
private void loadDefault() {
if (Nucleus.getPlugin().isTesting()) {
_defaultPack = new ResourcePack("_default", "http://respack.test.com", this);
_map.put("_default", _defaultPack);
return;
}
File file = new File("server.properties");
String resourcePack = FileUtils.scanTextFile(file, StandardCharsets.UTF_8, new IValidator<String>() {
@Override
public boolean isValid(String element) {
return element.startsWith(RESOURCE_PACK_KEY);
}
});
if (resourcePack == null || resourcePack.isEmpty() || resourcePack.equals(RESOURCE_PACK_KEY))
return;
resourcePack = PATTERN_UNESCAPE.matcher(
resourcePack.substring(RESOURCE_PACK_KEY.length() + 1)).replaceAll(":").trim();
_defaultPack = new ResourcePack("_default", resourcePack, this);
_map.put("_default", _defaultPack);
}
}