Add animation module

This commit is contained in:
ShinyDialga 2017-05-16 00:43:01 -05:00
parent 8220e90732
commit dafc229d09
8 changed files with 472 additions and 0 deletions

View File

@ -1,6 +1,7 @@
package tc.oc.pgm;
import tc.oc.commons.core.inject.HybridManifest;
import tc.oc.pgm.animation.AnimationManifest;
import tc.oc.pgm.broadcast.BroadcastManifest;
import tc.oc.pgm.classes.ClassManifest;
import tc.oc.pgm.controlpoint.ControlPointManifest;
@ -44,6 +45,7 @@ public class PGMModulesManifest extends HybridManifest {
install(new TeamManifest());
install(new TrackerManifest());
install(new StructureManifest());
install(new AnimationManifest());
install(new PickerManifest());
install(new ScoreboardManifest());
install(new DamageManifest());

View File

@ -0,0 +1,21 @@
package tc.oc.pgm.animation;
import org.bukkit.World;
import tc.oc.pgm.features.Feature;
import java.time.Duration;
import java.util.List;
public interface Animation extends Feature<AnimationDefinition> {
void place(Frame frame);
World getWorld();
Duration getAfter();
Duration getLoop();
int getCount();
List<Frame> getFrames();
}

View File

@ -0,0 +1,131 @@
package tc.oc.pgm.animation;
import com.google.inject.assistedinject.Assisted;
import org.bukkit.World;
import org.bukkit.util.ImVector;
import tc.oc.commons.core.inject.InnerFactory;
import tc.oc.pgm.features.FeatureDefinition;
import tc.oc.pgm.features.FeatureFactory;
import tc.oc.pgm.features.FeatureInfo;
import tc.oc.pgm.features.MatchFeatureContext;
import tc.oc.pgm.filters.FilterMatchModule;
import tc.oc.pgm.match.Match;
import javax.inject.Inject;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
@FeatureInfo(name = "animation")
public interface AnimationDefinition extends FeatureDefinition, FeatureFactory<Animation> {}
class AnimationDefinitionImpl extends FeatureDefinition.Impl implements AnimationDefinition {
interface Factory {
AnimationDefinitionImpl create(List<FrameDefinition> frames,
@Assisted("after") Duration after,
@Assisted("loop") Duration loop,
@Assisted("count") int count,
@Assisted("position") Optional<ImVector> position);
}
final @Inspect List<FrameDefinition> frames;
final @Inspect Duration after;
final @Inspect Duration loop;
final @Inspect int count;
final @Inspect Optional<ImVector> position;
private final InnerFactory<AnimationDefinitionImpl, AnimationImpl> factory;
@Inject
AnimationDefinitionImpl(@Assisted List<FrameDefinition> frames,
@Assisted("after") Duration after,
@Assisted("loop") Duration loop,
@Assisted("count") int count,
@Assisted("position") Optional<ImVector> position,
InnerFactory<AnimationDefinitionImpl, AnimationImpl> factory) {
this.frames = checkNotNull(frames);
this.after = after;
this.loop = loop;
this.count = count;
this.position = position;
this.factory = factory;
}
@Override
public AnimationImpl createFeature(Match match) {
return factory.create(this);
}
public void place(Frame frame, World world, ImVector offset) {
frame.place(world, offset);
}
@Override
public void load(Match match) {
match.features().get(this);
}
class AnimationImpl implements Animation {
final World world;
final Duration after;
final Duration loop;
final int count;
final List<Frame> frames;
@Inject AnimationImpl(World world, MatchFeatureContext features, AnimationScheduler scheduler, FilterMatchModule fmm) {
this.world = world;
final AnimationDefinitionImpl def = AnimationDefinitionImpl.this;
this.after = def.after;
this.loop = def.loop;
this.count = def.count;
this.frames = new ArrayList<>();
for (FrameDefinition frameDef : def.frames) {
Frame frame = features.get(frameDef);
frame.setOrigin(frameDef.origin());
frames.add(frame);
}
scheduler.animations.add(this);
}
@Override
public AnimationDefinition getDefinition() {
return AnimationDefinitionImpl.this;
}
@Override
public void place(Frame frame) {
frame.place(world, position.get().copy());
}
@Override
public World getWorld() {
return world;
}
@Override
public Duration getAfter() {
return after;
}
@Override
public Duration getLoop() {
return loop;
}
@Override
public int getCount() {
return count;
}
@Override
public List<Frame> getFrames() {
return frames;
}
}
}

View File

@ -0,0 +1,22 @@
package tc.oc.pgm.animation;
import tc.oc.commons.core.inject.HybridManifest;
import tc.oc.pgm.map.inject.MapBinders;
import tc.oc.pgm.map.inject.MapScoped;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.inject.MatchBinders;
import tc.oc.pgm.match.inject.MatchScoped;
public class AnimationManifest extends HybridManifest implements MapBinders, MatchBinders {
@Override
protected void configure() {
installInnerClassFactory(AnimationDefinitionImpl.AnimationImpl.class);
installFactory(AnimationDefinitionImpl.Factory.class);
bind(AnimationParser.class).in(MapScoped.class);
rootParsers().addBinding().to(AnimationParser.class);
bind(AnimationScheduler.class).in(MatchScoped.class);
matchListener(AnimationScheduler.class, MatchScope.LOADED);
}
}

View File

@ -0,0 +1,76 @@
package tc.oc.pgm.animation;
import com.google.common.collect.Range;
import org.bukkit.util.ImVector;
import org.bukkit.util.Vector;
import org.jdom2.Document;
import org.jdom2.Element;
import tc.oc.pgm.features.FeatureDefinitionContext;
import tc.oc.pgm.map.MapRootParser;
import tc.oc.pgm.regions.CuboidValidation;
import tc.oc.pgm.regions.RegionParser;
import tc.oc.pgm.utils.XMLUtils;
import tc.oc.pgm.xml.InvalidXMLException;
import tc.oc.pgm.xml.Node;
import tc.oc.pgm.xml.property.DurationProperty;
import tc.oc.pgm.xml.property.NumberProperty;
import tc.oc.pgm.xml.property.PropertyBuilderFactory;
import javax.inject.Inject;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class AnimationParser implements MapRootParser {
@Inject FeatureDefinitionContext features;
@Inject Document doc;
@Inject AnimationDefinitionImpl.Factory animationDefinitionFactory;
@Inject RegionParser regionParser;
@Inject PropertyBuilderFactory<Duration, DurationProperty> durations;
@Inject PropertyBuilderFactory<Integer, NumberProperty<Integer>> integers;
@Override
public void parse() throws InvalidXMLException {
for(Element elFrame : XMLUtils.flattenElements(doc.getRootElement(), "animations", "frame")) {
features.define(
elFrame,
new FrameDefinitionImpl(
XMLUtils.parseVector(elFrame.getAttribute("origin"), (Vector) null),
regionParser.property(elFrame, "region").validate(CuboidValidation.INSTANCE).required(),
XMLUtils.parseBoolean(elFrame.getAttribute("air"), false),
XMLUtils.parseBoolean(elFrame.getAttribute("clear"), true)
)
);
}
for(Element elAnimation : XMLUtils.flattenElements(doc.getRootElement(), "animations", "animation")) {
List<FrameDefinition> frames = new ArrayList<>();
for (Node elFrames : Node.fromChildren(elAnimation, "frames")) {
for (Node elFrame : Node.fromChildren(elFrames.asElement(), "frame")) {
frames.add(features.reference(Node.fromRequiredAttr(elFrame.asElement(), "id"), FrameDefinition.class));
}
}
Duration after = durations.property(elAnimation, "after").required();
Duration loop = durations.property(elAnimation, "loop").required();
int count = integers.property(elAnimation, "count")
.range(Range.atLeast(1))
.infinity(true)
.optional(Integer.MAX_VALUE);
final Optional<ImVector>
position = XMLUtils.parseVector(elAnimation, "location").optional(),
offset = XMLUtils.parseVector(elAnimation, "offset").optional();
if(position.isPresent() && offset.isPresent()) {
throw new InvalidXMLException("attributes 'location' and 'offset' cannot be used together", elAnimation);
}
features.define(elAnimation, animationDefinitionFactory.create(frames, after, loop, count, position));
}
}
}

View File

@ -0,0 +1,65 @@
package tc.oc.pgm.animation;
import com.google.common.collect.ImmutableList;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import tc.oc.commons.core.scheduler.Task;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.pgm.events.MatchBeginEvent;
import tc.oc.pgm.events.MatchEndEvent;
import tc.oc.pgm.match.MatchScheduler;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
public class AnimationScheduler implements Listener {
final List<AnimationDefinitionImpl.AnimationImpl> animations;
private final MatchScheduler scheduler;
private List<AnimationTask> tasks = ImmutableList.of();
@Inject AnimationScheduler(MatchScheduler scheduler) {
this.animations = new ArrayList<>();
this.scheduler = scheduler;
}
@EventHandler
public void matchBegin(MatchBeginEvent event) {
tasks = animations.stream()
.map(AnimationTask::new)
.collect(Collectors.toImmutableList());
}
@EventHandler
public void matchEnd(MatchEndEvent event) {
tasks.forEach(AnimationTask::cancel);
}
public class AnimationTask {
final AnimationDefinitionImpl.AnimationImpl animation;
final Task task;
int count = 0;
int currentFrame = 0;
AnimationTask(AnimationDefinitionImpl.AnimationImpl animation) {
this.animation = animation;
this.task = scheduler.createRepeatingTask(animation.getAfter(), animation.getLoop(), this::send);
}
void cancel() {
task.cancel();
}
void send() {
animation.place(animation.getFrames().get(currentFrame));
this.currentFrame = currentFrame >= animation.getFrames().size() - 1 ? 0 : currentFrame + 1;
if(++count >= animation.getCount()) {
cancel();
}
}
}
}

View File

@ -0,0 +1,20 @@
package tc.oc.pgm.animation;
import org.bukkit.World;
import org.bukkit.util.Vector;
import tc.oc.pgm.features.Feature;
/**
* Created from a {@link FrameDefinition} for a specific {@link World}.
*/
public interface Frame extends Feature<FrameDefinition> {
/**
* Place this frame in its origin world, offset by the given delta.
*/
void place(World world, Vector newLocation);
void setOrigin(Vector origin);
Vector getOrigin();
}

View File

@ -0,0 +1,135 @@
package tc.oc.pgm.animation;
import org.bukkit.World;
import org.bukkit.block.BlockImage;
import org.bukkit.geometry.Cuboid;
import org.bukkit.region.BlockRegion;
import org.bukkit.region.CuboidBlockRegion;
import org.bukkit.util.ImVector;
import org.bukkit.util.Vector;
import tc.oc.pgm.features.FeatureDefinition;
import tc.oc.pgm.features.FeatureFactory;
import tc.oc.pgm.features.FeatureInfo;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.regions.Region;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
@FeatureInfo(name = "frame")
public interface FrameDefinition extends FeatureDefinition, FeatureFactory<Frame> {
Vector origin();
Region region();
Cuboid bounds();
boolean includeAir();
boolean clearSource();
BlockRegion staticBlocks();
}
class FrameDefinitionImpl extends FeatureDefinition.Impl implements FrameDefinition {
private final @Inspect Region region;
private final @Inspect boolean includeAir;
private final @Inspect boolean clearSource;
// Lazy init because of feature proxies
private @Nullable ImVector origin;
private Cuboid bounds;
private BlockRegion staticBlocks;
public FrameDefinitionImpl(@Nullable Vector origin, Region region, boolean includeAir, boolean clearSource) {
this.origin = origin == null ? null : ImVector.copyOf(origin);
this.region = checkNotNull(region);
this.includeAir = includeAir;
this.clearSource = clearSource;
}
@Override
public Vector origin() {
if(origin == null) {
origin = region.getBounds().minimum();
}
return origin;
}
@Override
public Region region() {
return region;
}
@Override
public boolean includeAir() {
return includeAir;
}
@Override
public boolean clearSource() {
return clearSource;
}
@Override
public Cuboid bounds() {
if(bounds == null) {
bounds = region.getBounds();
}
return bounds;
}
@Override
public BlockRegion staticBlocks() {
if(staticBlocks == null) {
this.staticBlocks = CuboidBlockRegion.fromMinAndSize(bounds().minimumBlockInside(),
bounds().blockSize());
}
return staticBlocks;
}
@Override
public void load(Match match) {
match.features().get(this);
}
@Override
public Frame createFeature(Match match) {
return new FrameImpl(match.getWorld());
}
class FrameImpl implements Frame {
private final BlockImage image;
private Vector origin;
FrameImpl(World world) {
this.image = world.copyBlocks(staticBlocks(),
includeAir(),
clearSource());
this.origin = ImVector.ofZero();
}
@Override
public FrameDefinition getDefinition() {
return tc.oc.pgm.animation.FrameDefinitionImpl.this;
}
@Override
public void place(World world, Vector newLocation) {
world.pasteBlocks(image, newLocation.minus(origin));
}
@Override
public Vector getOrigin() {
return origin;
}
@Override
public void setOrigin(Vector origin) {
this.origin = origin;
}
}
}