/*
* 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.behavior;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lanternpowered.server.behavior.pipeline.BehaviorPipeline;
import org.lanternpowered.server.block.BlockSnapshotBuilder;
import org.lanternpowered.server.block.LanternBlockSnapshot;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.EntitySnapshot;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.item.inventory.transaction.SlotTransaction;
import org.spongepowered.api.util.Identifiable;
import org.spongepowered.api.world.BlockChangeFlag;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.annotation.Nullable;
public class BehaviorContextImpl implements BehaviorContext {
public static final class Snapshot {
private final Int2ObjectMap<Object> parameterValues;
private final Map<Location<World>, BlockSnapshot> blockSnapshots;
private final Set<BlockSnapshot> positionlessBlockSnapshots;
private final Set<SlotTransaction> slotTransactions;
private final Set<EntitySnapshot> entitySnapshots;
private final Cause cause;
Snapshot(Int2ObjectMap<Object> parameterValues, Map<Location<World>, BlockSnapshot> blockSnapshots,
Set<BlockSnapshot> positionlessBlockSnapshots, Set<SlotTransaction> slotTransactions,
Set<EntitySnapshot> entitySnapshots, Cause cause) {
this.parameterValues = parameterValues;
this.blockSnapshots = blockSnapshots;
this.positionlessBlockSnapshots = positionlessBlockSnapshots;
this.slotTransactions = slotTransactions;
this.entitySnapshots = entitySnapshots;
this.cause = cause;
}
}
private Cause cause;
private Int2ObjectMap<Object> parameterValues = new Int2ObjectOpenHashMap<>();
private Map<Location<World>, BlockSnapshot> blockSnapshots = new HashMap<>();
private Set<BlockSnapshot> positionlessBlockSnapshots = new HashSet<>();
private Set<SlotTransaction> slotTransactions = new HashSet<>();
private Set<EntitySnapshot> entitySnapshots = new HashSet<>();
public BehaviorContextImpl(Cause cause) {
this.cause = cause;
}
public Snapshot createSnapshot() {
return new Snapshot(new Int2ObjectOpenHashMap<>(this.parameterValues),
ImmutableMap.copyOf(this.blockSnapshots),
ImmutableSet.copyOf(this.positionlessBlockSnapshots),
ImmutableSet.copyOf(this.slotTransactions),
ImmutableSet.copyOf(this.entitySnapshots), this.cause);
}
public void restoreSnapshot(Snapshot snapshot) {
this.parameterValues = new Int2ObjectOpenHashMap<>(snapshot.parameterValues);
this.blockSnapshots = new HashMap<>(snapshot.blockSnapshots);
this.positionlessBlockSnapshots = new HashSet<>(snapshot.positionlessBlockSnapshots);
this.slotTransactions = new HashSet<>(snapshot.slotTransactions);
this.entitySnapshots = new HashSet<>(snapshot.entitySnapshots);
this.cause = snapshot.cause;
}
@Override
public <V> Optional<V> get(Parameter<V> parameter) {
checkNotNull(parameter, "parameter");
//noinspection unchecked
return Optional.ofNullable((V) this.parameterValues.get(parameter.getIndex()));
}
@Override
public <V> void set(Parameter<V> parameter, @Nullable V value) {
checkNotNull(parameter, "parameter");
if (value == null) {
this.parameterValues.remove(parameter.getIndex());
} else {
this.parameterValues.put(parameter.getIndex(), value);
}
}
@Override
public Cause getCause() {
return this.cause;
}
@Override
public void insertCause(String name, Object cause) {
this.cause = Cause.builder().from(this.cause).named(name, cause).build();
}
@Override
public void insertCause(NamedCause cause) {
this.cause = Cause.builder().from(this.cause).named(cause).build();
}
@Override
public void addEntity(EntitySnapshot entitySnapshot) {
}
@Override
public void addEntity(Entity entity) {
}
@Override
public void addSlotChange(SlotTransaction slotTransaction) {
this.slotTransactions.add(checkNotNull(slotTransaction, "slotTransaction"));
}
@Override
public void addSlotChanges(Iterable<SlotTransaction> slotTransactions) {
checkNotNull(slotTransactions, "slotTransactions");
slotTransactions.forEach(this::addSlotChange);
}
@Override
public List<SlotTransaction> getSlotChanges() {
return ImmutableList.copyOf(this.slotTransactions);
}
@Override
public void addBlockChange(BlockSnapshot blockSnapshot, boolean force) throws IllegalArgumentException {
checkNotNull(blockSnapshot, "blockSnapshot");
if (((LanternBlockSnapshot) blockSnapshot).isPositionless()) {
this.positionlessBlockSnapshots.add(blockSnapshot);
} else {
final Location<World> loc = blockSnapshot.getLocation().orElseThrow(
() -> new IllegalArgumentException("Unable to retrieve the location of the block snapshot, is the world loaded?"));
if (!force) {
checkArgument(this.blockSnapshots.putIfAbsent(loc, blockSnapshot) == null,
"There is already a block snapshot present for the location: %s", loc);
} else {
this.blockSnapshots.put(loc, blockSnapshot);
}
}
}
@Override
public void transformBlockChanges(BiConsumer<BlockSnapshot, BlockSnapshotBuilder> snapshotTransformer) {
checkNotNull(snapshotTransformer, "snapshotTransformer");
final Set<BlockSnapshot> newPositionlessBlockSnapshots = new HashSet<>();
final Map<Location<World>, BlockSnapshot> newBlockSnapshots = new HashMap<>();
final BlockSnapshotBuilder builder = BlockSnapshotBuilder.createPositionless();
for (BlockSnapshot blockSnapshot : Iterables.concat(this.positionlessBlockSnapshots, this.blockSnapshots.values())) {
builder.from(blockSnapshot);
snapshotTransformer.accept(blockSnapshot, builder);
final BlockSnapshot result = builder.build();
if (((LanternBlockSnapshot) result).isPositionless()) {
newPositionlessBlockSnapshots.add(result);
} else {
final Location<World> loc = blockSnapshot.getLocation().orElseThrow(
() -> new IllegalArgumentException("Unable to retrieve the location of the block snapshot, is the world loaded?"));
checkArgument(newBlockSnapshots.putIfAbsent(loc, result) == null,
"There is already a block snapshot present for the location: %s", loc);
}
}
this.positionlessBlockSnapshots = newPositionlessBlockSnapshots;
this.blockSnapshots = newBlockSnapshots;
}
@Override
public void transformBlockChangesWithFunction(Function<BlockSnapshot, BlockSnapshot> snapshotTransformer) {
checkNotNull(snapshotTransformer, "snapshotTransformer");
final Set<BlockSnapshot> newPositionlessBlockSnapshots = new HashSet<>();
final Map<Location<World>, BlockSnapshot> newBlockSnapshots = new HashMap<>();
final BlockSnapshotBuilder builder = BlockSnapshotBuilder.createPositionless();
for (BlockSnapshot blockSnapshot : Iterables.concat(this.positionlessBlockSnapshots, this.blockSnapshots.values())) {
final BlockSnapshot newSnapshot = snapshotTransformer.apply(blockSnapshot);
if (newSnapshot == null) {
continue;
}
final BlockSnapshot result = builder.build();
if (((LanternBlockSnapshot) result).isPositionless()) {
newPositionlessBlockSnapshots.add(result);
} else {
final Location<World> loc = blockSnapshot.getLocation().orElseThrow(
() -> new IllegalArgumentException("Unable to retrieve the location of the block snapshot, is the world loaded?"));
checkArgument(newBlockSnapshots.putIfAbsent(loc, blockSnapshot) == null,
"There is already a block snapshot present for the location: %s", loc);
}
}
this.positionlessBlockSnapshots = newPositionlessBlockSnapshots;
this.blockSnapshots = newBlockSnapshots;
}
@Override
public void populateBlockSnapshot(BlockSnapshot.Builder builder, int populationFlags) {
boolean creator = (populationFlags & PopulationFlags.CREATOR) != 0;
boolean notifier = (populationFlags & PopulationFlags.NOTIFIER) != 0;
if (creator || notifier) {
if (notifier) {
final Optional<Object> optNotifierObj = this.cause.get(NamedCause.NOTIFIER, Object.class);
if (optNotifierObj.isPresent()) {
final Object notifierObj = optNotifierObj.get();
if (notifierObj instanceof UUID) {
builder.notifier((UUID) notifierObj);
notifier = false; // Make sure that the notifier isn't overridden
} else if (notifierObj instanceof Identifiable) {
builder.notifier(((Identifiable) notifierObj).getUniqueId());
notifier = false; // Make sure that the notifier isn't overridden
}
}
}
final Optional<Entity> optEntity = this.cause.first(Entity.class);
if (optEntity.isPresent()) {
final UUID uuid = optEntity.get().getUniqueId();
if (creator) {
builder.creator(uuid);
}
if (notifier) {
builder.notifier(uuid);
}
}
}
}
@Override
public <B extends Behavior> boolean process(BehaviorPipeline<B> pipeline, BehaviorProcessFunction<B> function) {
Snapshot snapshot = null;
BehaviorResult result = null;
final Iterator<B> it = pipeline.getBehaviors().iterator();
while (it.hasNext()) {
final B behavior = it.next();
if (snapshot == null && it.hasNext()) {
snapshot = createSnapshot();
}
//noinspection unchecked
result = function.process(this, behavior);
if (result == BehaviorResult.SUCCESS) {
return true;
} else if (result == BehaviorResult.PASS) {
if (it.hasNext()) {
restoreSnapshot(snapshot);
}
} else if (result == BehaviorResult.FAIL) {
return false;
} else if (result == BehaviorResult.CONTINUE) {
if (it.hasNext()) {
snapshot = createSnapshot();
}
}
}
return !(result == null || result == BehaviorResult.PASS);
}
public void accept() {
for (Map.Entry<Location<World>, BlockSnapshot> entry : this.blockSnapshots.entrySet()) {
entry.getValue().restore(true, BlockChangeFlag.ALL);
}
}
}