/*
* 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;
import static com.sk89q.craftbook.sponge.util.locale.TranslationsManager.USE_PERMISSIONS;
import com.flowpowered.math.vector.Vector3d;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
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.types.SpongeBlockMechanic;
import com.sk89q.craftbook.sponge.util.BlockFilter;
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.type.TypeTokens;
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockType;
import org.spongepowered.api.block.BlockTypes;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.EntityTypes;
import org.spongepowered.api.entity.living.ArmorStand;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.block.InteractBlockEvent;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.event.cause.entity.spawn.SpawnCause;
import org.spongepowered.api.event.cause.entity.spawn.SpawnTypes;
import org.spongepowered.api.event.entity.RideEntityEvent;
import org.spongepowered.api.event.filter.cause.First;
import org.spongepowered.api.service.permission.PermissionDescription;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.util.Direction;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@Module(id = "chairs", name = "Chairs", onEnable="onInitialize", onDisable="onDisable")
public class Chairs extends SpongeBlockMechanic implements DocumentationProvider {
@Inject
@ModuleConfiguration
public ConfigurationNode config;
private ConfigValue<List<BlockFilter>> allowedBlocks = new ConfigValue<>("allowed-blocks", "A list of blocks that can be used.", getDefaultBlocks(), new TypeTokens.BlockFilterListTypeToken());
private ConfigValue<Boolean> exitAtEntry = new ConfigValue<>("exit-at-last-position", "Moves player's to their entry position when they exit the chair.", false);
private ConfigValue<Boolean> requireSigns = new ConfigValue<>("require-sign", "Require signs on the chairs.", false);
private ConfigValue<Integer> maxSignDistance = new ConfigValue<>("max-sign-distance", "The distance the sign can be from the clicked chair.", 3);
private ConfigValue<Boolean> healPassenger = new ConfigValue<>("heal-passenger", "Heal the player when they're sitting in the chair.", false);
private ConfigValue<Double> healAmount = new ConfigValue<>("heal-amount", "Amount to heal the player by.", 1.0d, TypeToken.of(Double.class));
private SpongePermissionNode usePermissions = new SpongePermissionNode("craftbook.chairs.use", "Allows the user to sit in chairs.", PermissionDescription.ROLE_USER);
private Map<UUID, Chair<?>> chairs;
@Override
public void onInitialize() throws CraftBookException {
super.onInitialize();
chairs = new HashMap<>();
allowedBlocks.load(config);
exitAtEntry.load(config);
requireSigns.load(config);
maxSignDistance.load(config);
healPassenger.load(config);
healAmount.load(config);
usePermissions.register();
Sponge.getGame().getScheduler().createTaskBuilder().intervalTicks(10).execute(task -> {
for (Map.Entry<UUID, Chair<?>> chair : new HashSet<>(chairs.entrySet())) {
Player player = Sponge.getGame().getServer().getPlayer(chair.getKey()).orElse(null);
if (player == null) {
removeChair(chair.getValue(), false);
return;
}
if (healPassenger.getValue()) {
if (player.get(Keys.HEALTH).orElse(0d) < player.get(Keys.MAX_HEALTH).orElse(0d)) {
player.offer(Keys.HEALTH, Math.min(player.get(Keys.HEALTH).orElse(0d) + healAmount.getValue(), player.get(Keys.MAX_HEALTH).orElse(0d)));
}
}
if (player.get(Keys.EXHAUSTION).orElse(-20d) > -20d) {
player.offer(Keys.EXHAUSTION, player.get(Keys.EXHAUSTION).orElse(-20d) - 0.1d);
}
chair.getValue().chairEntity.setRotation(new Vector3d(0, player.getRotation().getY(), 0));
}
}).submit(CraftBookPlugin.inst());
}
@Override
public void onDisable() {
super.onDisable();
chairs.clear();
}
@Override
public boolean isValid(Location<World> location) {
return BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), location.getBlock()) &&
(location.getBlockY() == 0 || !location.getRelative(Direction.DOWN).getBlockType().equals(BlockTypes.AIR));
}
private boolean hasSign(Location<World> location, List<Location<World>> searched, Location<World> original) {
boolean found = false;
for (Direction face : BlockUtil.getDirectFaces()) {
Location<World> otherBlock = location.getRelative(face);
if (searched.contains(otherBlock)) continue;
searched.add(otherBlock);
if (found) break;
if (location.getPosition().distanceSquared(original.getPosition()) > Math.pow(maxSignDistance.getValue(), 2)) continue;
if (SignUtil.isSign(otherBlock) && SignUtil.getFront(otherBlock) == face) {
found = true;
break;
}
if (Objects.equals(location.getBlockType(), otherBlock.getBlockType())) {
found = hasSign(otherBlock, searched, original);
}
}
return found;
}
private void addChair(Player player, Location<World> location) {
Entity entity = location.getExtent().createEntity(EntityTypes.ARMOR_STAND, location.getBlockPosition().toDouble().sub(-0.5, 1, -0.5));
entity.offer(Keys.INVISIBLE, true);
entity.offer(Keys.HAS_GRAVITY, false);
entity.setRotation(new Vector3d(0, player.getRotation().getY(), 0));
location.getExtent().spawnEntity(entity, Cause.of(NamedCause.of("root", SpawnCause.builder().type(SpawnTypes.CUSTOM).build()), NamedCause.source(player)));
Chair<?> chair = new Chair<>((ArmorStand) entity, location, player.getLocation());
entity.addPassenger(player);
player.sendMessage(Text.of(TextColors.YELLOW, "You sit down!"));
chairs.put(player.getUniqueId(), chair);
}
private void removeChair(Chair<?> chair, boolean clearPassengers) {
Player passenger = chair.chairEntity.getPassengers().stream().filter((entity -> entity instanceof Player)).map(e -> (Player) e).findFirst().orElse(null);
if (passenger != null && clearPassengers) {
passenger.setVehicle(null);
chair.chairEntity.clearPassengers();
passenger.sendMessage(Text.of(TextColors.YELLOW, "You stand up!"));
if (exitAtEntry.getValue()) {
Sponge.getScheduler().createTaskBuilder().delayTicks(5L).execute(() -> passenger.setLocation(chair.playerExitLocation)).submit(CraftBookPlugin.inst());
}
}
chair.chairEntity.remove();
chairs.values().remove(chair);
}
/**
* Gets the chair by the chair entity.
*
* @param entity The chair entity.
* @return The chair
*/
private Chair<?> getChair(Entity entity) {
return chairs.values().stream().filter((chair) -> chair.chairEntity.equals(entity)).findFirst().orElse(null);
}
/**
* Gets the chair by the chair block.
*
* @param location The chair block.
* @return The chair
*/
private Chair<?> getChair(Location<?> location) {
return chairs.values().stream().filter((chair) -> chair.chairLocation.getBlockPosition().equals(location.getBlockPosition())).findFirst().orElse(null);
}
@Listener
public void onBlockClick(InteractBlockEvent.Secondary.MainHand event, @First Player player) {
event.getTargetBlock().getLocation().ifPresent(location -> {
if (!isValid(location))
return;
if (player.get(Keys.IS_SNEAKING).orElse(false))
return;
if (!usePermissions.hasPermission(player)) {
player.sendMessage(USE_PERMISSIONS);
return;
}
if (requireSigns.getValue() && !hasSign(location, new ArrayList<>(), location)) {
return;
}
if (getChair(location) != null) {
player.sendMessage(Text.of(TextColors.RED, "Chair already occupied!"));
return;
}
if (chairs.containsKey(player.getUniqueId())) {
removeChair(chairs.get(player.getUniqueId()), true);
}
addChair(player, location);
});
}
@Listener
public void onBlockBreak(ChangeBlockEvent.Break event) {
event.getTransactions().forEach((transaction) -> transaction.getOriginal().getLocation().ifPresent((location) -> {
Chair<?> chair = getChair(location);
if (chair != null)
removeChair(chair, true);
}));
}
@Listener
public void onDismount(RideEntityEvent.Dismount event, @First Player player) {
if (event.getTargetEntity() instanceof ArmorStand) {
Chair<?> chair = getChair(event.getTargetEntity());
if (chair != null) {
player.sendMessage(Text.of(TextColors.YELLOW, "You stand up!"));
Sponge.getScheduler().createTaskBuilder().execute(() -> {
removeChair(chair, false);
if (!exitAtEntry.getValue()) {
Sponge.getGame().getTeleportHelper().getSafeLocation(chair.chairLocation).ifPresent(player::setLocation);
}
}).submit(CraftBookPlugin.inst());
}
}
}
private static List<BlockFilter> getDefaultBlocks() {
List<BlockFilter> states = Lists.newArrayList();
states.addAll(Sponge.getRegistry().getAllOf(BlockType.class).stream()
.filter(blockType -> blockType.getName().toLowerCase().contains("stairs"))
.map(blockType -> new BlockFilter(blockType.getName())).collect(Collectors.toList()));
return states;
}
@Override
public String getPath() {
return "mechanics/chairs";
}
@Override
public ConfigValue<?>[] getConfigurationNodes() {
return new ConfigValue[]{
allowedBlocks,
exitAtEntry,
requireSigns,
maxSignDistance,
healPassenger,
healAmount
};
}
@Override
public PermissionNode[] getPermissionNodes() {
return new PermissionNode[]{
usePermissions
};
}
/**
* Data class that stores information necessary
* to the functionality of chairs.
*/
static final class Chair<T extends Entity> {
private T chairEntity;
private Location<World> chairLocation;
private Location<World> playerExitLocation;
Chair(T chairEntity, Location<World> chairLocation, Location<World> playerExitLocation) {
this.chairEntity = chairEntity;
this.chairLocation = chairLocation;
this.playerExitLocation = playerExitLocation;
}
}
}