Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I hacked in a spectator-like follow mode #115

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 87 additions & 4 deletions src/main/java/net/xolt/freecam/Freecam.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder.Living;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.input.Input;
import net.minecraft.client.input.KeyboardInput;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.option.Perspective;
import net.minecraft.client.util.InputUtil;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.text.Text;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Vec3d;
import net.xolt.freecam.config.ModConfig;
import net.xolt.freecam.util.FreeCamera;
import net.xolt.freecam.util.FreecamPosition;
Expand All @@ -27,12 +32,16 @@ public class Freecam implements ClientModInitializer {
private static KeyBinding playerControlBind;
private static KeyBinding tripodResetBind;
private static KeyBinding configGuiBind;
private static KeyBinding followBind;
private static KeyBinding setFollowBind;
private static boolean freecamEnabled = false;
private static boolean tripodEnabled = false;
private static boolean followEnabled = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isEnabled method should also check this. Currently various mixins are broken, e.g. "Show Player".

private static boolean playerControlEnabled = false;
private static boolean disableNextTick = false;
private static Integer activeTripod = null;
private static FreeCamera freeCamera;
private static LivingEntity followMe;
private static HashMap<Integer, FreecamPosition> overworld_tripods = new HashMap<>();
private static HashMap<Integer, FreecamPosition> nether_tripods = new HashMap<>();
private static HashMap<Integer, FreecamPosition> end_tripods = new HashMap<>();
Expand All @@ -49,6 +58,10 @@ public void onInitializeClient() {
"key.freecam.tripodReset", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, "category.freecam.freecam"));
configGuiBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.freecam.configGui", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, "category.freecam.freecam"));
followBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.freecam.follow", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, "category.freecam.freecam"));
setFollowBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.freecam.setFollow", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, "category.freecam.freecam"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm in favor of hooking into vanilla's Pick Block action instead of adding our own keybind, however if we do add a custom bind I'd rather it be one single bind than two separate ones.

Also, the keybind translation key should be added to the lang file.


ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (tripodResetBind.isPressed()) {
Expand All @@ -70,6 +83,16 @@ public void onInitializeClient() {
} else if (freecamBind.wasPressed()) {
toggle();
while (freecamBind.wasPressed()) {}
} else if (followBind.wasPressed()) {
toggleFollow();
while (freecamBind.wasPressed()) {}
} else if (setFollowBind.wasPressed()) {
final LivingEntity entity = raycastEntity();
if (entity != null) {
followMe = entity;
toggleFollow();
}
while (setFollowBind.wasPressed()) {}
}

while (playerControlBind.wasPressed()) {
Expand All @@ -82,6 +105,54 @@ public void onInitializeClient() {
});
}

public static LivingEntity raycastEntity() {
if (!(MC.crosshairTarget instanceof EntityHitResult entityHit)) {
return null;
}

Entity lookedAtEntity = entityHit.getEntity();
if (lookedAtEntity == null) {
return null;
}

if (!(lookedAtEntity instanceof LivingEntity follow)) {
return null;
}

return follow;
}

public static void toggleFollow() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toggleFollow might benefit from being split into enableFollow and disableFollow methods?

if (followEnabled) {
followEnabled = false;
onDisable();
return;
}

if (followMe == null) {
followMe = raycastEntity();
}

if (followMe == null) {
return;
}

followEnabled = true;
MC.setCameraEntity(followMe);

if (!freecamEnabled) {
onEnable();
return;
}

freeCamera.despawn();
freeCamera.input = new Input();
freeCamera = null;

freecamEnabled = false;
return;
}

public static void toggle() {
if (tripodEnabled) {
toggleTripod(activeTripod);
Expand Down Expand Up @@ -181,8 +252,17 @@ private static void onDisableTripod() {
}

private static void onEnableFreecam() {
onEnable();
if (!followEnabled) {
onEnable();
}

freeCamera = new FreeCamera(-420);

if (followEnabled) {
freeCamera.applyPosition(new FreecamPosition(followMe));
followEnabled = false;
}
Comment on lines +261 to +264
Copy link
Member

@MattSturgeon MattSturgeon Nov 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use FreecamPosition.getSwimmingPosition() instead of new FreecamPosition(). (Currently activating freecam from follow mode will position the camera at the feet instead of at the head of followMe.)

It may be cleaner to remove the public FreeCamera(int id) constructor entirely and instead do something like:

freeCamera = new FreeCamera(-420, FreecamPosition.getSwimmingPosition(
    followEnabled ? followMe : MC.player
));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, calling onEnable() conditionally and then manually disabling followEnabled without any sorta disableFollow method feels a bit messy.


freeCamera.applyPerspective(ModConfig.INSTANCE.visual.perspective, ModConfig.INSTANCE.collision.alwaysCheck || !ModConfig.INSTANCE.collision.ignoreAll);
freeCamera.spawn();
MC.setCameraEntity(freeCamera);
Expand Down Expand Up @@ -217,9 +297,12 @@ private static void onDisable() {
MC.gameRenderer.setRenderHand(true);
MC.setCameraEntity(MC.player);
playerControlEnabled = false;
freeCamera.despawn();
freeCamera.input = new Input();
freeCamera = null;

if (freeCamera != null) {
freeCamera.despawn();
freeCamera.input = new Input();
freeCamera = null;
}

if (MC.player != null) {
MC.player.input = new KeyboardInput(MC.options);
Expand Down