package net.t7seven7t.craftfx.effect;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapMaker;
import net.t7seven7t.craftfx.CraftFX;
import net.t7seven7t.craftfx.data.ConfigDataHolder;
import net.t7seven7t.craftfx.data.Data;
import net.t7seven7t.craftfx.data.effect.DelayData;
import net.t7seven7t.craftfx.data.effect.ExtentData;
import net.t7seven7t.craftfx.data.effect.TargetSelectorData;
import net.t7seven7t.craftfx.data.effect.TimerData;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
*
*/
public final class Effect extends ConfigDataHolder {
private final Map<ExtentState, Consumer<EffectContext>> consumerMap;
/**
* Map that holds contexts for effects with an unfinished extent state
*/
private Map<Player, EffectContext> contextMap;
Effect(ConfigurationSection config,
Map<ExtentState, Consumer<EffectContext>> consumerMap) {
super(config);
this.consumerMap = ImmutableMap.copyOf(consumerMap);
}
public static Builder builder() {
return new Builder();
}
/**
* Runs this effect in the context provided
*
* @param context context
*/
public void run(EffectContext context) {
final EffectContext oldContext = getLastContext(context.getInitiator());
ExtentState state;
if (oldContext != null) {
state = oldContext.getProperty("extent-state", ExtentState.class)
.map(ExtentState::other).orElse(null);
// remove old context
context.copyState(oldContext);
removeContext(context.getInitiator());
} else {
final ExtentData data = getData(ExtentData.class).get();
state = data.isInverted() ? ExtentState.END : ExtentState.START;
if (data.isExtentDisabled(state)) {
state = state.other();
} else if (!data.isExtentDisabled(state.other())) {
if (context.getTrigger().isCanceller()) {
// cancellers don't want to create new state
return;
}
addContext(context.getInitiator(), context);
context.setProperty("extent-state", state);
}
}
// run the effect for the retrieved state
final Consumer<EffectContext> consumer = consumerMap.get(state);
if (consumer == null) return;
final TargetSelectorData selectorData = getData(TargetSelectorData.class).get();
context.selector = CraftFX.instance().getTargetSelectorRegistry()
.getSpec(selectorData.getMode()).get()
.newTargetSelector(context.getOrigin(), context.getInitiator(), this);
final TimerData timerData = getData(TimerData.class).get();
final long delay = getData(DelayData.class).get().getDelayTicks();
if (timerData.getIterations() > 1) {
final int iterations = timerData.getIterations();
new BukkitRunnable() {
private int iteration = 0;
@Override
public void run() {
consumer.accept(context);
if (++iteration >= iterations) cancel();
}
}.runTaskTimer(CraftFX.plugin(), delay, timerData.getInterval());
} else if (delay == 0) {
consumer.accept(context);
} else {
Bukkit.getScheduler().runTaskLater(CraftFX.plugin(),
() -> consumer.accept(context), delay);
}
}
@Override
public String toString() {
return "Effect{"
+ getConfig().getCurrentPath()
+ "}";
}
private void addContext(Player player, EffectContext context) {
if (contextMap == null) {
contextMap = new MapMaker().weakKeys().makeMap();
}
contextMap.put(player, context);
}
private void removeContext(Player player) {
if (contextMap != null) contextMap.remove(player);
}
private EffectContext getLastContext(Player player) {
return contextMap == null ? null : contextMap.get(player);
}
public static class Builder {
private final ConfigurationSection config = new MemoryConfiguration();
private final List<Data> dataList = new ArrayList<>();
private final Map<ExtentState, Consumer<EffectContext>> consumerMap = new HashMap<>();
public Builder spec(String alias) {
CraftFX.instance().getEffectRegistry().getSpec(alias).ifPresent(this::spec);
return this;
}
public Builder spec(EffectSpec spec) {
this.consumerMap.putAll(spec.consumerMap);
return this;
}
public Builder data(Data data) {
this.dataList.add(data);
return this;
}
public Builder consumer(ExtentState state, Consumer<EffectContext> consumer) {
consumerMap.put(state, consumer);
return this;
}
public Builder consumer(Consumer<EffectContext> consumer) {
consumerMap.put(ExtentState.END, consumer);
consumerMap.put(ExtentState.START, consumer);
return this;
}
public Builder property(String propertyName, Object value) {
config.set(propertyName, value);
return this;
}
public Effect build() {
final Effect effect = new Effect(config, consumerMap);
dataList.forEach(effect::offer);
return effect;
}
}
}