Skip to content
Merged
Show file tree
Hide file tree
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
40 changes: 40 additions & 0 deletions game/src/main/java/net/onelitefeather/cygnus/Cygnus.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import net.minestom.server.listener.EntityActionListener;
import net.minestom.server.network.packet.client.play.ClientEntityActionPacket;
import net.onelitefeather.cygnus.ambient.AmbientProvider;
import net.onelitefeather.cygnus.command.GlitchCommand;
import net.onelitefeather.cygnus.command.StartCommand;
import net.onelitefeather.cygnus.common.ListenerHandling;
import net.onelitefeather.cygnus.common.bootstrap.ServiceBootstrap;
Expand All @@ -46,6 +47,7 @@
import net.onelitefeather.cygnus.common.page.PageProvider;
import net.onelitefeather.cygnus.common.page.event.PageExpiredEvent;
import net.onelitefeather.cygnus.event.GameFinishEvent;
import net.onelitefeather.cygnus.gaze.SlenderGazeService;
import net.onelitefeather.cygnus.event.SlenderReviveEvent;
import net.onelitefeather.cygnus.event.StaminaStateChangeEvent;
import net.onelitefeather.cygnus.jumpscare.JumpScareManager;
Expand Down Expand Up @@ -74,14 +76,19 @@
import net.onelitefeather.cygnus.resourcepack.ResourcePackService;
import net.onelitefeather.cygnus.stamina.SlenderBarTrigger;
import net.onelitefeather.cygnus.stamina.StaminaService;
import net.onelitefeather.cygnus.overlay.ScreenOverlay;
import net.onelitefeather.cygnus.overlay.EquipmentScreenOverlay;
import net.onelitefeather.cygnus.overlay.OverlayProperties;
import net.onelitefeather.cygnus.utils.StaminaHelper;
import net.onelitefeather.cygnus.utils.ViewRuleUpdater;
import net.onelitefeather.cygnus.view.GameView;
import net.onelitefeather.cygnus.view.GameViewImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

/**
Expand All @@ -103,6 +110,8 @@ public final class Cygnus implements TeamCreator, ListenerHandling {
private final JumpScareManager jumpscareManager;
private final SpectatorService spectatorService;
private final Optional<ResourcePackService> resourcePackService;
private final ScreenOverlay screenOverlay;
private final SlenderGazeService slenderGazeService;

public Cygnus() {
Path path = ServiceBootstrap.resolveWorkingDirectory();
Expand All @@ -126,6 +135,8 @@ public Cygnus() {
.orElseThrow(() -> new IllegalStateException("Spectator team not found"));
this.spectatorService = new SpectatorService(spectatorTeam, survivorTeam);
this.resourcePackService = ResourcePackService.create();
this.screenOverlay = new EquipmentScreenOverlay();
this.slenderGazeService = new SlenderGazeService(this.screenOverlay, this::currentSlender);
this.initPhases();
this.initCommands();
this.initListener();
Expand All @@ -136,6 +147,29 @@ public Cygnus() {
private void initCommands() {
var manager = MinecraftServer.getCommandManager();
manager.register(new StartCommand(this.linearPhaseSeries));
manager.register(new GlitchCommand(this.slenderGazeService));
}

/**
* Looks up the player currently playing the slender.
*
* @return the slender, or {@code null} while the role is unassigned
*/
private @Nullable Player currentSlender() {
return this.teamService.getTeam(GameConfig.SLENDER_KEY)
.flatMap(team -> team.getPlayers().stream().findFirst())
.orElse(null);
}

/**
* Collects the players that are currently survivors.
*
* @return the survivor team's players
*/
private Set<Player> currentSurvivors() {
return this.teamService.getTeam(GameConfig.SURVIVOR_KEY)
.map(team -> Set.copyOf(team.getPlayers()))
.orElseGet(Set::of);
}


Expand Down Expand Up @@ -191,6 +225,12 @@ private void registerGameListener() {
MinecraftServer.getPacketListenerManager().setPlayListener(ClientEntityActionPacket.class, CygnusEntityActionListener::listener);

spectatorService.registerListener(handler);

// Without the pack the vignette font does not exist and survivors would stare at an
// empty box, so the effect stays off wherever the pack is not delivered.
if (OverlayProperties.enabled()) {
this.slenderGazeService.registerListener(handler, this::currentSurvivors);
}
}

private void initPhases() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.onelitefeather.cygnus.command;

import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.onelitefeather.cygnus.common.Messages;
import net.onelitefeather.cygnus.gaze.SlenderGaze;
import net.onelitefeather.cygnus.gaze.SlenderGazeService;

/**
* Puts the slender's glitch on screen without him being there, so the drawings can be judged from
* the lobby.
* <p>
* {@code /glitch <1-4>} holds one level, {@code /glitch off} takes it away.
* </p>
*
* @author TheMeinerLP
* @version 1.0.0
* @since 2.7.0
*/
public final class GlitchCommand extends Command {

/**
* Creates the command.
*
* @param service the service that draws the tearing
*/
public GlitchCommand(SlenderGazeService service) {
super("glitch");

var level = ArgumentType.Integer("level").between(1, SlenderGaze.LEVELS);

this.setDefaultExecutor((sender, context) -> sender.sendMessage(
Messages.withMiniPrefix("<gray>Usage: <yellow>/glitch <1-" + SlenderGaze.LEVELS + "> | off")
));

this.addSyntax((sender, context) -> {
Player player = CommandSenders.asPlayer(sender, "have a view to lose.");
if (player == null) return;
service.show(player, context.get(level) - 1);
}, level);

this.addSyntax((sender, context) -> {
Player player = CommandSenders.asPlayer(sender, "have a view to lose.");
if (player == null) return;
service.hide(player);
}, ArgumentType.Literal("off"));
}
}
69 changes: 69 additions & 0 deletions game/src/main/java/net/onelitefeather/cygnus/gaze/SlenderGaze.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.onelitefeather.cygnus.gaze;

import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.onelitefeather.cygnus.common.util.Helper;

/**
* Works out how badly the sight of the slender tears a survivor's view apart.
* <p>
* This is about seeing him, not about him being there: standing behind a survivor does nothing at
* all, however close he is. Only once he is inside their field of view does the picture start to
* come apart, and it gets worse the nearer he is.
* </p>
*
* @author TheMeinerLP
* @version 1.0.0
* @since 2.7.0
*/
public final class SlenderGaze {

/** Nothing to draw: he is out of range, or out of sight. */
public static final int NONE = -1;

/** How many degrees of tearing there are between just visible and right in front. */
public static final int LEVELS = 4;

/** Beyond this distance he is too far away to unsettle anything. */
private static final double RANGE = 32.0D;

/** The distance at which the tearing is at its worst. */
private static final double CLOSE = 6.0D;

/**
* How far off the view direction he may stand and still count as seen. Roughly the horizontal
* field of view of a default client — the effect belongs on the screen he is on.
*/
private static final double FIELD_OF_VIEW = 0.55D;

/** Below this distance the direction to him carries no meaning any more. */
private static final double DISTANCE_EPSILON = 1.0E-6D;

private SlenderGaze() {
}

/**
* Works out the tearing a survivor gets from where the slender stands.
*
* @param survivor the survivor's position, whose yaw and pitch supply the view direction
* @param slender the slender's position
* @return a level between {@code 0} and {@code LEVELS - 1}, or {@link #NONE}
*/
public static int levelOf(Pos survivor, Pos slender) {
double distance = survivor.distance(slender);
if (distance > RANGE) return NONE;
if (distance < DISTANCE_EPSILON) return LEVELS - 1;

Vec towardsSlender = new Vec(
slender.x() - survivor.x(),
slender.y() - survivor.y(),
slender.z() - survivor.z()
).div(distance);

if (survivor.direction().dot(towardsSlender) < FIELD_OF_VIEW) return NONE;

double nearness = (RANGE - distance) / (RANGE - CLOSE);
double clamped = Helper.clamp(nearness, 0.0D, 1.0D);
return (int) Math.round(clamped * (LEVELS - 1));
}
}
Loading
Loading