/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* 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 org.lanternpowered.server.block.state;
import static com.google.common.base.Preconditions.checkNotNull;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import org.lanternpowered.server.block.LanternBlockSnapshot;
import org.lanternpowered.server.block.LanternBlockType;
import org.lanternpowered.server.block.trait.LanternBlockTrait;
import org.lanternpowered.server.catalog.AbstractCatalogType;
import org.lanternpowered.server.catalog.PluginCatalogType;
import org.lanternpowered.server.data.property.AbstractDirectionRelativePropertyHolder;
import org.lanternpowered.server.data.value.mutable.LanternValue;
import org.spongepowered.api.CatalogType;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.trait.BlockTrait;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataQuery;
import org.spongepowered.api.data.MemoryDataContainer;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.manipulator.ImmutableDataManipulator;
import org.spongepowered.api.data.merge.MergeFunction;
import org.spongepowered.api.data.value.BaseValue;
import org.spongepowered.api.data.value.immutable.ImmutableValue;
import org.spongepowered.api.data.value.mutable.Value;
import org.spongepowered.api.util.Cycleable;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@SuppressWarnings({"rawtypes", "unchecked"})
public final class LanternBlockState extends AbstractCatalogType implements PluginCatalogType, BlockState, AbstractDirectionRelativePropertyHolder {
// A lookup table to get a specific state when you would change a value
ImmutableTable<BlockTrait<?>, Comparable<?>, BlockState> propertyValueTable;
// The values for every attached trait
final ImmutableMap<BlockTrait<?>, Comparable<?>> traitValues;
// The lookup to convert between key <--> trait
private final ImmutableMap<Key<Value<?>>, BlockTrait<?>> keyToBlockTrait;
// The base block state
private final LanternBlockStateMap baseState;
// The name of the block state
private final String name;
private final String id;
// A internal id that is used for faster lookups
int internalId;
// Whether this state is extended
boolean extended;
LanternBlockState(LanternBlockStateMap baseState, ImmutableMap<BlockTrait<?>, Comparable<?>> traitValues) {
this.traitValues = traitValues;
this.baseState = baseState;
ImmutableBiMap.Builder<Key<Value<?>>, BlockTrait<?>> builder = ImmutableBiMap.builder();
for (BlockTrait trait : traitValues.keySet()) {
builder.put(((LanternBlockTrait) trait).getKey(), trait);
}
this.keyToBlockTrait = builder.build();
StringBuilder idBuilder = new StringBuilder();
idBuilder.append(baseState.getBlockType().getId().substring(baseState.getBlockType().getPluginId().length() + 1));
if (!traitValues.isEmpty()) {
idBuilder.append('[');
Joiner joiner = Joiner.on(',');
List<String> propertyValues = new ArrayList<>();
for (Map.Entry<BlockTrait<?>, Comparable<?>> entry : traitValues.entrySet()) {
propertyValues.add(entry.getKey().getName() + "=" + entry.getValue().toString().toLowerCase(Locale.ENGLISH));
}
idBuilder.append(joiner.join(propertyValues));
idBuilder.append(']');
}
this.name = idBuilder.toString();
this.id = baseState.getBlockType().getPluginId() + ':' + this.name;
}
public int getInternalId() {
return this.internalId;
}
@Override
public int getContentVersion() {
return 0;
}
@Override
public DataContainer toContainer() {
final DataContainer dataContainer = new MemoryDataContainer();
dataContainer.set(DataQuery.of("BlockType"), this.baseState.getBlockType().getId());
for (Map.Entry<BlockTrait<?>, Comparable<?>> entry : this.traitValues.entrySet()) {
Object value = entry.getValue();
dataContainer.set(((LanternBlockTrait) entry.getKey()).getKey().getQuery(),
value instanceof CatalogType ? ((CatalogType) value).getId() : value);
}
return dataContainer;
}
@Override
public LanternBlockType getType() {
return this.baseState.getBlockType();
}
@Override
public BlockState withExtendedProperties(Location<World> location) {
return this.baseState.getBlockType().getExtendedBlockStateProvider().get(this, checkNotNull(location, "location"), null);
}
@Override
public <T extends ImmutableDataManipulator<?, ?>> Optional<T> get(Class<T> containerClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends ImmutableDataManipulator<?, ?>> Optional<T> getOrCreate(Class<T> containerClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(Class<? extends ImmutableDataManipulator<?, ?>> containerClass) {
// TODO Auto-generated method stub
return false;
}
@Override
public <E> Optional<BlockState> transform(Key<? extends BaseValue<E>> key, Function<E, E> function) {
if (!this.supports(key)) {
return Optional.empty();
} else {
E current = this.get(key).get();
final E newVal = checkNotNull(function.apply(current));
return this.with(key, newVal);
}
}
@Override
public <E> Optional<BlockState> with(Key<? extends BaseValue<E>> key, E value) {
BlockTrait trait;
if (!this.supports(key) || !(trait = this.keyToBlockTrait.get(key)).getPredicate().test(value)) {
return Optional.empty();
}
if (this.traitValues.get(trait) == value) {
return Optional.of(this);
}
return Optional.of(this.propertyValueTable.row(trait).get(value));
}
@Override
public Optional<BlockState> with(BaseValue<?> value) {
if (!this.supports(value)) {
return Optional.empty();
}
BlockTrait trait = this.keyToBlockTrait.get(value.getKey());
if (this.traitValues.get(trait) == value.get()) {
return Optional.of(this);
}
return Optional.of(this.propertyValueTable.row(trait).get(value.get()));
}
@Override
public Optional<BlockState> with(ImmutableDataManipulator<?, ?> valueContainer) {
Optional<BlockState> state = null;
for (ImmutableValue<?> value : valueContainer.getValues()) {
state = this.with(value);
if (!state.isPresent()) {
return state;
}
}
return state == null ? Optional.of(this) : state;
}
@Override
public Optional<BlockState> with(Iterable<ImmutableDataManipulator<?, ?>> valueContainers) {
Optional<BlockState> state = null;
for (ImmutableDataManipulator<?, ?> valueContainer : valueContainers) {
state = this.with(valueContainer);
if (!state.isPresent()) {
return state;
}
}
return state == null ? Optional.of(this) : state;
}
@Override
public Optional<BlockState> without(Class<? extends ImmutableDataManipulator<?, ?>> containerClass) {
// You cannot remove any data manipulators from a block state
return Optional.empty();
}
@Override
public BlockState merge(BlockState that) {
if (!this.getType().equals(that.getType())) {
return this;
} else {
BlockState temp = this;
for (ImmutableDataManipulator<?, ?> manipulator : that.getManipulators()) {
Optional<BlockState> optional = temp.with(manipulator);
if (optional.isPresent()) {
temp = optional.get();
} else {
return temp;
}
}
return temp;
}
}
@Override
public BlockState merge(BlockState that, MergeFunction function) {
if (!getType().equals(that.getType())) {
return this;
} else {
BlockState temp = this;
for (ImmutableDataManipulator<?, ?> manipulator : that.getManipulators()) {
@Nullable ImmutableDataManipulator old = temp.get(manipulator.getClass()).orElse(null);
Optional<BlockState> optional = temp.with(checkNotNull(function.merge(old, manipulator)));
if (optional.isPresent()) {
temp = optional.get();
} else {
return temp;
}
}
return temp;
}
}
@Override
public <E> Optional<E> get(Key<? extends BaseValue<E>> key) {
if (!this.supports(key)) {
return Optional.empty();
}
return Optional.ofNullable((E) this.traitValues.get(this.keyToBlockTrait.get(key)));
}
@Override
public <E, V extends BaseValue<E>> Optional<V> getValue(Key<V> key) {
if (!this.supports(key)) {
return Optional.empty();
}
BlockTrait<?> blockTrait = this.keyToBlockTrait.get(key);
return Optional.of((V) new LanternValue(key, this.traitValues.get(blockTrait)));
}
@Override
public boolean supports(Key<?> key) {
return this.keyToBlockTrait.containsKey(checkNotNull(key, "key"));
}
@Override
public BlockState copy() {
return this;
}
@Override
public ImmutableSet<Key<?>> getKeys() {
return this.baseState.keys;
}
@Override
public ImmutableSet<ImmutableValue<?>> getValues() {
// TODO Auto-generated method stub
return null;
}
@Override
public BlockState cycleValue(Key<? extends BaseValue<? extends Cycleable<?>>> key) {
if (!this.supports(key)) {
return this;
}
return this.cycleTraitValue(this.keyToBlockTrait.get(key)).get();
}
@Override
public BlockSnapshot snapshotFor(Location<World> location) {
final World world = location.getExtent();
final Vector3i pos = location.getBlockPosition();
// TODO: Tile entity data
return new LanternBlockSnapshot(location, this, this,
world.getCreator(pos), world.getNotifier(pos), ImmutableMap.of());
}
@Override
public List<ImmutableDataManipulator<?, ?>> getManipulators() {
// TODO Auto-generated method stub
return null;
}
@Override
public List<ImmutableDataManipulator<?, ?>> getContainers() {
// TODO Auto-generated method stub
return null;
}
/**
* Cycles to the next possible value of the block trait and returns
* the new block state. Will return absent if the block trait or
* the value isn't supported.
*
* @param blockTrait the block trait
* @return the block state if successful
*/
public <T extends Comparable<T>> Optional<BlockState> cycleTraitValue(BlockTrait<T> blockTrait) {
checkNotNull(blockTrait, "blockTrait");
if (!this.supportsTrait(blockTrait)) {
return Optional.empty();
}
T value = (T) this.traitValues.get(blockTrait);
if (value instanceof Cycleable) {
T last = value;
T next;
while ((next = (T) ((Cycleable) last).cycleNext()) != value) {
if (blockTrait.getPredicate().test(next)) {
value = next;
break;
}
last = next;
}
} else {
Iterator<T> it = blockTrait.getPossibleValues().iterator();
while (it.hasNext()) {
if (it.next() == value) {
if (it.hasNext()) {
value = it.next();
} else {
value = blockTrait.getPossibleValues().iterator().next();
}
}
}
}
return this.withTrait(blockTrait, value);
}
@Override
public Optional<BlockState> withTrait(BlockTrait<?> trait, Object value) {
checkNotNull(trait, "trait");
checkNotNull(value, "value");
if (value instanceof String) {
if (!this.supportsTrait(trait)) {
return Optional.empty();
}
for (Object object : trait.getPossibleValues()) {
if (object.toString().equals(value) || (object instanceof CatalogType && ((CatalogType) object).getId()
.equalsIgnoreCase((String) value))) {
value = object;
break;
}
}
if (value instanceof String) {
return Optional.empty();
}
}
if (!supportsTraitValue(trait, value)) {
return Optional.empty();
}
if (this.traitValues.get(trait) == value) {
return Optional.of(this);
}
return Optional.of(this.propertyValueTable.row(trait).get(value));
}
/**
* Gets whether this block state the specified trait supports.
*
* @param blockTrait the block trait
* @return whether the block trait is supported
*/
public boolean supportsTrait(BlockTrait<?> blockTrait) {
return this.traitValues.containsKey(checkNotNull(blockTrait, "blockTrait"));
}
/**
* Gets whether this block state the specified trait supports.
*
* @param blockTrait the block trait
* @param value the value
* @return whether the block trait and value are supported
*/
public boolean supportsTraitValue(BlockTrait<?> blockTrait, Object value) {
return this.supportsTrait(checkNotNull(blockTrait, "blockTrait")) &&
((Predicate) blockTrait.getPredicate()).test(checkNotNull(value, "value"));
}
public boolean isExtended() {
return this.extended;
}
@Override
public <T extends Comparable<T>> Optional<T> getTraitValue(BlockTrait<T> blockTrait) {
return Optional.ofNullable((T) this.traitValues.get(checkNotNull(blockTrait, "blockTrait")));
}
@Override
public Optional<BlockTrait<?>> getTrait(String blockTrait) {
return this.baseState.getTrait(checkNotNull(blockTrait, "blockTrait"));
}
@Override
public Collection<BlockTrait<?>> getTraits() {
return this.traitValues.keySet();
}
@Override
public Collection<?> getTraitValues() {
return this.traitValues.values();
}
@Override
public Map<BlockTrait<?>, ?> getTraitMap() {
return this.traitValues;
}
@Override
public String getId() {
return this.id;
}
@Override
public String getPluginId() {
return this.baseState.getBlockType().getPluginId();
}
@Override
public String getName() {
return this.name;
}
}