forked from Clones/Controlify
vibration conflict support - multiple vibrations can play at once
This commit is contained in:
@ -17,14 +17,25 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RumbleState nextState() {
|
||||
public void tick() {
|
||||
tick++;
|
||||
if (tick >= keyframes.length)
|
||||
finished = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RumbleState currentState() {
|
||||
if (tick == 0)
|
||||
throw new IllegalStateException("Effect hasn't ticked yet.");
|
||||
|
||||
return keyframes[tick - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int age() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
@ -137,9 +148,4 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
public ContinuousRumbleEffect continuous() {
|
||||
int lastIndex = this.states().length - 1;
|
||||
return new ContinuousRumbleEffect(index -> this.states()[index % lastIndex], this.priority());
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,34 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
private final Function<Integer, RumbleState> stateFunction;
|
||||
private final int priority;
|
||||
private final int timeout;
|
||||
private final int minTime;
|
||||
private int tick;
|
||||
private boolean stopped;
|
||||
|
||||
public ContinuousRumbleEffect(Function<Integer, RumbleState> stateFunction) {
|
||||
this(stateFunction, 0);
|
||||
}
|
||||
|
||||
public ContinuousRumbleEffect(Function<Integer, RumbleState> stateFunction, int priority) {
|
||||
public ContinuousRumbleEffect(Function<Integer, RumbleState> stateFunction, int priority, int timeout, int minTime) {
|
||||
this.stateFunction = stateFunction;
|
||||
this.priority = priority;
|
||||
this.timeout = timeout;
|
||||
this.minTime = minTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RumbleState nextState() {
|
||||
public void tick() {
|
||||
tick++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RumbleState currentState() {
|
||||
if (tick == 0)
|
||||
throw new IllegalStateException("Effect hasn't ticked yet.");
|
||||
|
||||
return stateFunction.apply(tick - 1);
|
||||
}
|
||||
|
||||
@ -27,17 +36,72 @@ public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
public int currentTick() {
|
||||
@Override
|
||||
public int age() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return stopped;
|
||||
return (stopped || (timeout > 0 && tick >= timeout)) && tick >= minTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Function<Integer, RumbleState> stateFunction;
|
||||
private int priority;
|
||||
private int timeout = -1;
|
||||
private int minTime;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder byTick(Function<Integer, RumbleState> stateFunction) {
|
||||
this.stateFunction = stateFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder constant(RumbleState state) {
|
||||
this.stateFunction = tick -> state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder constant(float strong, float weak) {
|
||||
return this.constant(new RumbleState(strong, weak));
|
||||
}
|
||||
|
||||
public Builder timeout(int timeoutTicks) {
|
||||
Validate.isTrue(timeoutTicks >= 0, "the timeout cannot be negative!");
|
||||
|
||||
this.timeout = timeoutTicks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minTime(int minTimeTicks) {
|
||||
Validate.isTrue(minTimeTicks >= 0, "the minimum time cannot be negative!");
|
||||
|
||||
this.minTime = minTimeTicks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder priority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContinuousRumbleEffect build() {
|
||||
Validate.notNull(stateFunction, "stateFunction cannot be null!");
|
||||
Validate.isTrue(minTime <= timeout || timeout == -1, "the minimum time cannot be greater than the timeout!");
|
||||
|
||||
return new ContinuousRumbleEffect(stateFunction, priority, timeout, minTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
public interface RumbleEffect {
|
||||
RumbleState nextState();
|
||||
public interface RumbleEffect extends Comparable<RumbleEffect> {
|
||||
void tick();
|
||||
RumbleState currentState();
|
||||
|
||||
boolean isFinished();
|
||||
|
||||
int priority();
|
||||
int age();
|
||||
|
||||
@Override
|
||||
default int compareTo(RumbleEffect o) {
|
||||
int priorityCompare = Integer.compare(o.priority(), this.priority());
|
||||
if (priorityCompare != 0) return priorityCompare;
|
||||
return Integer.compare(this.age(), o.age());
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
|
||||
public class RumbleManager {
|
||||
private final RumbleCapable controller;
|
||||
private RumbleEffectInstance playingEffect;
|
||||
private final Queue<RumbleEffectInstance> effectQueue;
|
||||
|
||||
public RumbleManager(RumbleCapable controller) {
|
||||
this.controller = controller;
|
||||
this.effectQueue = new PriorityQueue<>(Comparator.comparing(RumbleEffectInstance::effect));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@ -17,34 +24,49 @@ public class RumbleManager {
|
||||
if (!controller.canRumble())
|
||||
return;
|
||||
|
||||
playingEffect = new RumbleEffectInstance(source, effect);
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return playingEffect != null;
|
||||
}
|
||||
|
||||
public void stopCurrentEffect() {
|
||||
if (playingEffect == null)
|
||||
return;
|
||||
|
||||
controller.setRumble(0f, 0f, RumbleSource.MASTER);
|
||||
playingEffect = null;
|
||||
effectQueue.add(new RumbleEffectInstance(source, effect));
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (playingEffect == null)
|
||||
return;
|
||||
RumbleEffectInstance effect;
|
||||
do {
|
||||
effect = effectQueue.peek();
|
||||
|
||||
if (playingEffect.effect().isFinished()) {
|
||||
stopCurrentEffect();
|
||||
// if we have no effects, break out of loop and get the null check
|
||||
if (effect == null)
|
||||
break;
|
||||
|
||||
// if the effect is finished, remove and set null, so we loop again
|
||||
if (effect.effect().isFinished()) {
|
||||
effectQueue.remove(effect);
|
||||
effect = null;
|
||||
}
|
||||
} while (effect == null);
|
||||
|
||||
if (effect == null) {
|
||||
controller.setRumble(0f, 0f, RumbleSource.MASTER);
|
||||
return;
|
||||
}
|
||||
|
||||
RumbleState state = playingEffect.effect().nextState();
|
||||
controller.setRumble(state.strong(), state.weak(), playingEffect.source());
|
||||
effectQueue.removeIf(e -> e.effect().isFinished());
|
||||
effectQueue.forEach(e -> e.effect().tick());
|
||||
|
||||
RumbleState state = effect.effect().currentState();
|
||||
controller.setRumble(state.strong(), state.weak(), effect.source());
|
||||
}
|
||||
|
||||
private record RumbleEffectInstance(RumbleSource source, RumbleEffect effect) {
|
||||
public void clearEffects() {
|
||||
effectQueue.clear();
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return !effectQueue.isEmpty();
|
||||
}
|
||||
|
||||
private record RumbleEffectInstance(RumbleSource source, RumbleEffect effect) implements Comparable<RumbleEffectInstance> {
|
||||
@Override
|
||||
public int compareTo(@NotNull RumbleManager.RumbleEffectInstance o) {
|
||||
return effect.compareTo(o.effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user