Rewrite controls

This commit is contained in:
Sollace 2020-05-06 15:55:25 +02:00
parent 019c9e17ed
commit ee7c67afe4
8 changed files with 234 additions and 111 deletions

View file

@ -1,41 +1,40 @@
package com.minelittlepony.unicopia.ability;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import org.lwjgl.glfw.GLFW;
import java.util.Set;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.MutableRegistry;
import net.minecraft.util.registry.SimpleRegistry;
public interface Abilities {
Map<Identifier, Integer> KEYS_CODES = new HashMap<>();
Map<AbilitySlot, Set<Ability<?>>> BY_SLOT = new EnumMap<>(AbilitySlot.class);
MutableRegistry<Ability<?>> REGISTRY = new SimpleRegistry<>();
// unicorn / alicorn
Ability<?> TELEPORT = register(new UnicornTeleportAbility(), "teleport", GLFW.GLFW_KEY_O);
Ability<?> CAST = register(new UnicornCastingAbility(), "cast", GLFW.GLFW_KEY_P);
Ability<?> TELEPORT = register(new UnicornTeleportAbility(), "teleport", AbilitySlot.PRIMARY);
Ability<?> CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.SECONDARY);
// earth / alicorn
Ability<?> GROW = register(new EarthPonyGrowAbility(), "grow", GLFW.GLFW_KEY_N);
Ability<?> STOMP = register(new EarthPonyStompAbility(), "stomp", GLFW.GLFW_KEY_M);
Ability<?> STOMP = register(new EarthPonyStompAbility(), "stomp", AbilitySlot.PRIMARY);
Ability<?> GROW = register(new EarthPonyGrowAbility(), "grow", AbilitySlot.SECONDARY);
// pegasus / bat / alicorn / changeling
Ability<?> CARRY = register(new CarryAbility(), "carry", GLFW.GLFW_KEY_K);
Ability<?> CARRY = register(new CarryAbility(), "carry", AbilitySlot.PASSIVE);
// pegasus / alicorn
Ability<?> CLOUD = register(new PegasusCloudInteractionAbility(), "cloud", GLFW.GLFW_KEY_J);
Ability<?> CLOUD = register(new PegasusCloudInteractionAbility(), "cloud", AbilitySlot.TERTIARY);
// changeling
Ability<?> FEED = register(new ChangelingFeedAbility(), "feed", GLFW.GLFW_KEY_O);
Ability<?> TRAP = register(new ChangelingTrapAbility(), "trap", GLFW.GLFW_KEY_L);
Ability<?> DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", AbilitySlot.PRIMARY);
Ability<?> FEED = register(new ChangelingFeedAbility(), "feed", AbilitySlot.SECONDARY);
Ability<?> TRAP = register(new ChangelingTrapAbility(), "trap", AbilitySlot.TERTIARY);
Ability<?> DISGUISE = register(new ChangelingDisguiseAbility(), "disguise", GLFW.GLFW_KEY_P);
static <T extends Ability<?>> T register(T power, String name, int keyCode) {
static <T extends Ability<?>> T register(T power, String name, AbilitySlot slot) {
Identifier id = new Identifier("unicopia", name);
KEYS_CODES.put(id, keyCode);
BY_SLOT.computeIfAbsent(slot, s -> new HashSet<>()).add(power);
return REGISTRY.add(id, power);
}
}

View file

@ -1,9 +1,12 @@
package com.minelittlepony.unicopia.ability;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.Updatable;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -18,15 +21,7 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable {
private final Pony player;
/**
* Ticks of warmup before an ability is triggered.
*/
private int warmup;
/**
* Ticks of cooldown after an ability has been triggered.
*/
private int cooldown;
private final Map<AbilitySlot, Stat> stats = new EnumMap<>(AbilitySlot.class);
/**
* True once the current ability has been triggered.
@ -35,69 +30,90 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable {
private Optional<Ability<?>> activeAbility = Optional.empty();
private AbilitySlot activeSlot = AbilitySlot.NONE;
public AbilityDispatcher(Pony player) {
this.player = player;
}
/**
* Returns true if the currrent ability can we swapped out.
* Returns true if the current ability can we swapped out.
*/
boolean canSwitchStates() {
return !activeAbility.isPresent() || (warmup != 0) || (triggered && cooldown == 0);
return !activeAbility.isPresent() || getStat(getActiveSlot()).canSwitchStates();
}
public void tryUseAbility(Ability<?> power) {
public AbilitySlot getActiveSlot() {
return activeSlot;
}
public void cancelAbility(AbilitySlot slot) {
if (getActiveSlot() == slot && canSwitchStates()) {
setActiveAbility(slot, null);
}
}
public void activate(AbilitySlot slot) {
if (canSwitchStates()) {
setAbility(power);
getAbility(slot).ifPresent(ability -> setActiveAbility(slot, ability));
}
}
public void tryClearAbility() {
if (canSwitchStates()) {
setAbility(null);
}
public Stat getStat(AbilitySlot slot) {
return stats.computeIfAbsent(slot, Stat::new);
}
protected synchronized void setAbility(Ability<?> power) {
public Optional<Ability<?>> getAbility(AbilitySlot slot) {
Race race = player.getSpecies();
return Abilities.BY_SLOT.get(slot).stream().filter(a -> a.canUse(race)).findFirst();
}
protected synchronized void setActiveAbility(AbilitySlot slot, Ability<?> power) {
if (activeAbility.orElse(null) != power) {
activeSlot = slot;
triggered = false;
activeAbility = Optional.ofNullable(power);
warmup = activeAbility.map(p -> p.getWarmupTime(player)).orElse(0);
cooldown = 0;
Stat stat = getStat(slot);
stat.setWarmup(activeAbility.map(p -> p.getWarmupTime(player)).orElse(0));
stat.setCooldown(0);
}
}
@Nullable
protected synchronized Optional<Ability<?>> getUsableAbility() {
protected synchronized Optional<Ability<?>> getActiveAbility() {
Stat stat = getStat(getActiveSlot());
return activeAbility.filter(ability -> {
return (!(ability == null || (triggered && warmup == 0 && cooldown == 0)) && ability.canUse(player.getSpecies()));
return (!(ability == null || (triggered && stat.warmup == 0 && stat.cooldown == 0)) && ability.canUse(player.getSpecies()));
});
}
public int getRemainingCooldown() {
return cooldown;
}
@Override
public void onUpdate() {
getUsableAbility().ifPresent(this::activate);
getActiveAbility().ifPresent(this::activate);
}
private <T extends Hit> void activate(Ability<T> ability) {
if (warmup > 0) {
warmup--;
Stat stat = getStat(getActiveSlot());
stats.values().forEach(s -> {
if (s != stat) {
s.idle();
}
});
if (stat.warmup > 0) {
stat.warmup--;
System.out.println("warming up");
ability.preApply(player);
return;
}
if (cooldown > 0) {
cooldown--;
if (stat.tickInactive()) {
System.out.println("cooling down");
ability.postApply(player);
if (cooldown <= 0) {
setAbility(null);
if (stat.cooldown <= 0) {
setActiveAbility(AbilitySlot.NONE, null);
}
return;
}
@ -108,7 +124,7 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable {
if (ability.canActivate(player.getWorld(), player)) {
triggered = true;
cooldown = ability.getCooldownTime(player);
stat.setCooldown(ability.getCooldownTime(player));
if (player.isClientPlayer()) {
T data = ability.tryActivate(player);
@ -116,22 +132,28 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable {
if (data != null) {
Channel.PLAYER_ABILITY.send(new MsgPlayerAbility<>(ability, data));
} else {
cooldown = 0;
stat.setCooldown(0);
}
}
}
if (cooldown <= 0) {
setAbility(null);
if (stat.cooldown <= 0) {
setActiveAbility(AbilitySlot.NONE, null);
}
}
@Override
public void toNBT(CompoundTag compound) {
compound.putBoolean("triggered", triggered);
compound.putInt("warmup", warmup);
compound.putInt("cooldown", cooldown);
getUsableAbility().ifPresent(ability -> {
if (compound.contains("stats")) {
stats.clear();
CompoundTag li = compound.getCompound("stats");
li.getKeys().forEach(key -> {
getStat(AbilitySlot.valueOf(key)).fromNBT(li.getCompound(key));
});
}
compound.putInt("activeSlot", activeSlot.ordinal());
getActiveAbility().ifPresent(ability -> {
compound.putString("activeAbility", Abilities.REGISTRY.getId(ability).toString());
});
}
@ -139,8 +161,97 @@ public class AbilityDispatcher implements Updatable, NbtSerialisable {
@Override
public void fromNBT(CompoundTag compound) {
triggered = compound.getBoolean("triggered");
warmup = compound.getInt("warmup");
cooldown = compound.getInt("cooldown");
CompoundTag li = new CompoundTag();
stats.forEach((key, value) -> li.put(key.name(), value.toNBT()));
compound.put("stats", li);
activeSlot = compound.contains("activeSlot") ? AbilitySlot.values()[compound.getInt("activeSlot")] : activeSlot;
activeAbility = Abilities.REGISTRY.getOrEmpty(new Identifier(compound.getString("activeAbility")));
}
public class Stat implements NbtSerialisable {
/**
* Ticks of warmup before an ability is triggered.
*/
private int warmup;
private int maxWarmup;
/**
* Ticks of cooldown after an ability has been triggered.
*/
private int cooldown;
private int maxCooldown;
public final AbilitySlot slot;
private Stat(AbilitySlot slot) {
this.slot = slot;
}
/**
* Returns true if the current ability can we swapped out.
*/
boolean canSwitchStates() {
return (warmup != 0) || (triggered && cooldown == 0);
}
public int getRemainingCooldown() {
return cooldown;
}
public float getFillProgress() {
float cooldown = getWarmup();
if (cooldown <= 0 || cooldown >= 1) {
return getCooldown();
}
return 1 - cooldown;
}
public float getCooldown() {
return maxCooldown <= 0 ? 0 : ((float)cooldown / (float)maxCooldown);
}
public void setCooldown(int value) {
cooldown = value;
maxCooldown = value;
}
public float getWarmup() {
return maxWarmup <= 0 ? 0 : ((float)warmup / (float)maxWarmup);
}
public void setWarmup(int value) {
maxWarmup = value;
warmup = value;
}
public void idle() {
if (warmup > 0) {
warmup--;
}
if (cooldown > 0) {
cooldown--;
}
}
public boolean tickInactive() {
return cooldown > 0 && cooldown-- > 0;
}
@Override
public void toNBT(CompoundTag compound) {
compound.putInt("warmup", warmup);
compound.putInt("cooldown", cooldown);
compound.putInt("maxWarmup", maxWarmup);
compound.putInt("maxCooldown", maxCooldown);
}
@Override
public void fromNBT(CompoundTag compound) {
warmup = compound.getInt("warmup");
cooldown = compound.getInt("cooldown");
maxWarmup = compound.getInt("maxWarmup");
maxCooldown = compound.getInt("maxCooldown");
}
}
}

View file

@ -0,0 +1,13 @@
package com.minelittlepony.unicopia.ability;
public enum AbilitySlot {
NONE,
PRIMARY,
SECONDARY,
TERTIARY,
PASSIVE;
public boolean isPassive() {
return this == PASSIVE;
}
}

View file

@ -193,7 +193,7 @@ public class EarthPonyStompAbility implements Ability<Multi> {
@Override
public void postApply(Pony player) {
int timeDiff = getCooldownTime(player) - player.getAbilities().getRemainingCooldown();
int timeDiff = getCooldownTime(player) - player.getAbilities().getStat(player.getAbilities().getActiveSlot()).getRemainingCooldown();
if (player.getOwner().getEntityWorld().getTime() % 1 == 0 || timeDiff == 0) {
spawnParticleRing(player.getOwner(), timeDiff, 1);

View file

@ -1,17 +1,13 @@
package com.minelittlepony.unicopia.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.data.Hit;
import org.lwjgl.glfw.GLFW;
import com.minelittlepony.unicopia.ability.AbilitySlot;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding;
@ -24,27 +20,25 @@ import net.minecraft.util.Identifier;
class KeyBindingsHandler {
private final String KEY_CATEGORY = "unicopia.category.name";
private final Map<InputUtil.KeyCode, List<Ability<? extends Hit>>> keyPools = new HashMap<>();
private final Set<KeyBinding> bindings = new HashSet<>();
private final Map<KeyBinding, AbilitySlot> keys = new HashMap<>();
private final Set<KeyBinding> pressed = new HashSet<>();
private Collection<Ability<?>> getKeyCodePool(KeyBinding keyCode) {
return keyPools.computeIfAbsent(keyCode.getDefaultKeyCode(), i -> new ArrayList<>());
}
public void addKeybind(Ability<?> p) {
public KeyBindingsHandler() {
KeyBindingRegistry.INSTANCE.addCategory(KEY_CATEGORY);
Identifier id = Abilities.REGISTRY.getId(p);
int code = Abilities.KEYS_CODES.get(id);
addKeybind(GLFW.GLFW_KEY_O, AbilitySlot.PRIMARY);
addKeybind(GLFW.GLFW_KEY_P, AbilitySlot.SECONDARY);
addKeybind(GLFW.GLFW_KEY_L, AbilitySlot.TERTIARY);
}
FabricKeyBinding b = FabricKeyBinding.Builder.create(id, InputUtil.Type.KEYSYM, code, KEY_CATEGORY).build();
public void addKeybind(int code, AbilitySlot slot) {
KeyBindingRegistry.INSTANCE.addCategory(KEY_CATEGORY);
FabricKeyBinding b = FabricKeyBinding.Builder.create(new Identifier("unicopia", slot.name().toLowerCase()), InputUtil.Type.KEYSYM, code, KEY_CATEGORY).build();
KeyBindingRegistry.INSTANCE.register(b);
getKeyCodePool(b).add(p);
bindings.add(b);
keys.put(b, slot);
}
public void tick(MinecraftClient client) {
@ -54,19 +48,20 @@ class KeyBindingsHandler {
}
Pony iplayer = Pony.of(client.player);
for (KeyBinding i : bindings) {
for (KeyBinding i : keys.keySet()) {
AbilitySlot slot = keys.get(i);
if (slot == AbilitySlot.PRIMARY && client.options.keySneak.isPressed()) {
slot = AbilitySlot.PASSIVE;
}
if (i.isPressed()) {
if (pressed.add(i)) {
System.out.println("key press " + i);
Race race = iplayer.getSpecies();
getKeyCodePool(i).stream()
.filter(power -> power.canUse(race))
.findFirst()
.ifPresent(iplayer.getAbilities()::tryUseAbility);
System.out.println("Key down " + slot);
iplayer.getAbilities().activate(slot);
}
} else if (pressed.remove(i)) {
System.out.println("key release " + i);
iplayer.getAbilities().tryClearAbility();
System.out.println("Key up " + slot);
iplayer.getAbilities().cancelAbility(slot);
}
}
}

View file

@ -4,10 +4,8 @@ import static com.minelittlepony.unicopia.EquinePredicates.PLAYER_UNICORN;
import javax.annotation.Nullable;
import com.minelittlepony.common.event.ClientReadyCallback;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.container.SpellbookResultSlot;
import com.minelittlepony.unicopia.ducks.Colourful;
@ -45,8 +43,6 @@ public class UnicopiaClient implements ClientModInitializer {
URenderers.bootstrap();
ClientTickCallback.EVENT.register(this::tick);
ClientReadyCallback.EVENT.register(client -> Abilities.REGISTRY.stream().forEach(keyboard::addKeybind));
DefaultTexturesRegistry.getDefaultTextures().add(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, SpellbookResultSlot.EMPTY_GEM_SLOT));
ColorProviderRegistry.ITEM.register((stack, tint) -> {

View file

@ -1,5 +1,8 @@
package com.minelittlepony.unicopia.client.gui;
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
import com.minelittlepony.unicopia.ability.AbilitySlot;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
@ -14,8 +17,8 @@ public class UHud extends DrawableHelper {
public static final Identifier HUD_TEXTURE = new Identifier("unicopia", "textures/gui/hud.png");
private Slot secondarySlot = new Slot(26, 0);
private Slot tertiarySlot = new Slot(36, 24);
private Slot secondarySlot = new Slot(AbilitySlot.SECONDARY, 26, 0);
private Slot tertiarySlot = new Slot(AbilitySlot.TERTIARY, 36, 24);
public void render(InGameHud hud, float tickDelta) {
MinecraftClient client = MinecraftClient.getInstance();
@ -34,55 +37,60 @@ public class UHud extends DrawableHelper {
int frameHeight = 54;
int frameWidth = 54;
AbilityDispatcher abilities = Pony.of(client.player).getAbilities();
blit(x, y, 0, 0, frameWidth, frameHeight, 128, 128); // background
float progressPercent = 0.25F;
float progressPercent = abilities.getStat(AbilitySlot.PRIMARY).getFillProgress();
if (progressPercent > 0 && progressPercent < 1) {
int progressHeight = (int)(frameHeight * progressPercent);
blit(x, y + (frameHeight - progressHeight),
61, frameHeight - progressHeight,
frameWidth, progressHeight, 128, 128); // progress
}
blit(x, y, 0, 54, frameWidth, frameHeight, 128, 128); // frame
secondarySlot.render(x, y, 50, 100, tickDelta);
tertiarySlot.render(x, y, 5, 10, tickDelta);
secondarySlot.render(abilities, x, y, tickDelta);
tertiarySlot.render(abilities, x, y, tickDelta);
RenderSystem.disableBlend();
RenderSystem.disableAlphaTest();
}
static class Slot {
private final AbilitySlot slot;
private int x;
private int y;
private float lastCooldown;
public Slot(int x, int y) {
public Slot(AbilitySlot slot, int x, int y) {
this.slot = slot;
this.x = x;
this.y = y;
}
void render(int x, int y, float cooldown, float maxCooldown, float tickDelta) {
void render(AbilityDispatcher abilities, int x, int y, float tickDelta) {
x += this.x;
y += this.y;
if (cooldown > 0 && maxCooldown > 0 && cooldown < maxCooldown) {
float cooldown = abilities.getStat(slot).getFillProgress();
if (cooldown > 0 && cooldown < 1) {
float lerpCooldown = MathHelper.lerp(tickDelta, cooldown, lastCooldown);
lastCooldown = lerpCooldown;
float cooldownPercent = 1 - lerpCooldown / maxCooldown;
int slotPadding = 4;
int slotSize = 15;
int progressBottom = y + slotPadding + slotSize;
int progressTop = progressBottom - (int)(15F * cooldownPercent);
int progressTop = progressBottom - (int)(15F * cooldown);
fill(x + slotPadding, progressTop, x + slotPadding + slotSize, progressBottom, 0xAAFFFFFF);
}

View file

@ -4,9 +4,9 @@ import javax.annotation.Nullable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
import com.minelittlepony.unicopia.ducks.PonyContainer;
import com.minelittlepony.unicopia.enchanting.PageOwner;
import com.minelittlepony.unicopia.entity.FlightControl;
import com.minelittlepony.unicopia.entity.Ponylike;
import com.minelittlepony.unicopia.entity.RaceContainer;
import com.minelittlepony.unicopia.magic.Caster;
import com.minelittlepony.unicopia.network.Transmittable;
@ -97,9 +97,10 @@ public interface Pony extends Caster<PlayerEntity>, RaceContainer<PlayerEntity>,
return InteractionManager.instance().isClientPlayer(getOwner());
}
@SuppressWarnings("unchecked")
@Nullable
static Pony of(@Nullable PlayerEntity player) {
return Ponylike.<Pony>of(player);
return player == null ? null : ((PonyContainer<Pony>)player).get();
}
static boolean equal(GameProfile one, GameProfile two) {