Add animation module
This commit is contained in:
parent
8220e90732
commit
dafc229d09
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue