diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfig.java b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfig.java index 40fa4c1e..628e3686 100644 --- a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfig.java +++ b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfig.java @@ -1,40 +1,51 @@ package net.onelitefeather.cygnus.common.config; import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.net.URI; /** - * The {@link GameConfig} interface represents the structure for a configuration which is used by the game. - * It contains some values which can be adjusted to change specific settings for the game. - * There are also some static values in the interface which are also used in the game. - * Each static value indicates that it is a constant value and should not be changed. + * The configuration of a game, grouped by the feature each value belongs to. + *
+ * Every group checks its own values when it is created, so a configuration read from the file, the + * defaults and one built in a test go through the same rules. The static values are constants of + * the game itself and cannot be configured. + *
* + * @param round the player limits and timings of a round + * @param teams the team sizes + * @param sentryDsn the DSN Sentry reports to, or {@code null} to keep the integration off + * @param resourcePack where the client gets the ResourcePack from + * @param pageProximity the sound that hints at a nearby page + * @param damageSound the sound a player hears when hit + * @param glitch how the sight of the slender tears a survivor's view + * @param slenderStatic the static the slender hears while pages are found + * @param lobbyAtmosphereShare how far the lobby's atmosphere is taken towards the map's own: {@code 0} + * leaves the vanilla overworld, {@code 1} is exactly the map's atmosphere * @author theEvilReaper - * @version 1.5.0 + * @version 2.0.0 * @since 1.0.0 */ -public sealed interface GameConfig permits GameConfigImpl, InternalGameConfig { - - /* The name of the Slender team. - */ - String SLENDER_TEAM_NAME = "Slender"; - - Key SLENDER_KEY = Key.key("cygnus", "slender"); - /** - * The name of the Survivor team. - */ - String SURVIVOR_TEAM_NAME = "Survivor"; - - Key SURVIVOR_KEY = Key.key("cygnus", "survivor"); - - String SPECTATOR_TEAM_NAME = "Spectator"; - - Key SPECTATOR_KEY = Key.key("cygnus", "spectator"); - - int MIN_ACTIVE_PAGE_COUNT = 4 * 2; +public record GameConfig( + Round round, + Teams teams, + @Nullable String sentryDsn, + ResourcePack resourcePack, + PageProximity pageProximity, + DamageSound damageSound, + Glitch glitch, + SlenderStatic slenderStatic, + float lobbyAtmosphereShare +) { + + public static final String SLENDER_TEAM_NAME = "Slender"; + public static final Key SLENDER_KEY = Key.key("cygnus", "slender"); + public static final String SURVIVOR_TEAM_NAME = "Survivor"; + public static final Key SURVIVOR_KEY = Key.key("cygnus", "survivor"); + public static final Key SPECTATOR_KEY = Key.key("cygnus", "spectator"); + + public static final int MIN_ACTIVE_PAGE_COUNT = 4 * 2; /** * How many seconds after a round starts before the first pages spawn. @@ -45,19 +56,15 @@ public sealed interface GameConfig permits GameConfigImpl, InternalGameConfig { * * @since 2.15.0 */ - int PAGE_SPAWN_DELAY = 10; + public static final int PAGE_SPAWN_DELAY = 10; /** * How many seconds {@link #PAGE_SPAWN_DELAY} may randomly shift up or down, re-rolled every - * round. - *- * Without this the delay lands on the exact same tick every round, which players learn and - * plan around; the jitter keeps the moment the first pages appear unpredictable. - *
+ * round, so the moment the first pages appear stays unpredictable. * * @since 2.15.0 */ - int PAGE_SPAWN_DELAY_JITTER = 2; + public static final int PAGE_SPAWN_DELAY_JITTER = 2; /** * How many seconds a found page stays hidden when there is no free spot left to move it to. @@ -68,7 +75,7 @@ public sealed interface GameConfig permits GameConfigImpl, InternalGameConfig { * * @since 2.15.0 */ - int PAGE_RESPAWN_DELAY = 15; + public static final int PAGE_RESPAWN_DELAY = 15; /** * How many seconds {@link #PAGE_RESPAWN_DELAY} may randomly shift up or down, re-rolled for every @@ -76,696 +83,262 @@ public sealed interface GameConfig permits GameConfigImpl, InternalGameConfig { * * @since 2.15.0 */ - int PAGE_RESPAWN_DELAY_JITTER = 5; - - int PAGE_TTL_TIME = 60; - - int FORCE_START_TIME = 11; - - int MIN_PAGE_COUNT = 8; - - /** - * The sound played to a survivor while a page is within {@link #pageProximityRange()}. - * The amethyst chime is a soft, bell-less shimmer that reads as "something is here" without - * sounding like an alarm. - */ - Key DEFAULT_PAGE_PROXIMITY_SOUND = Key.key("block.amethyst_block.chime"); - - /** - * The largest {@link #pageProximityRange()} a configuration may ask for. Beyond this a single - * page would be audible across a good part of the map, which stops being a hint. - */ - int MAX_PAGE_PROXIMITY_RANGE = 64; - - /** - * The {@link #pageProximityVolumeFactor()} a configuration gets when it says nothing. - *- * A factor of 1 makes the chime reach exactly to {@link #pageProximityRange()} and no further, - * which means it fades to silence precisely where the hint is supposed to start being useful. - * Doubling that leaves roughly half the volume at the edge of the range while the service still - * clips on the range itself. - *
- */ - float DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR = 2.0F; + public static final int PAGE_RESPAWN_DELAY_JITTER = 5; - /** - * The largest {@link #pageProximityVolumeFactor()} a configuration may ask for. - *- * The ceiling is not about loudness - Minecraft caps a sound's amplitude at the source - * regardless of volume - but about the falloff. The flatter it gets, the more evenly loud the - * chime is across the whole range, until a player can no longer tell a page two blocks away - * from one at the edge. Past 8 that distance cue is gone. - *
- */ - float MAX_PAGE_PROXIMITY_VOLUME_FACTOR = 8.0F; + public static final int PAGE_TTL_TIME = 60; + public static final int FORCE_START_TIME = 11; + public static final int MIN_PAGE_COUNT = 8; /** - * The sound played to a player who was just hit. - *- * The vanilla hurt sound, because that is exactly what is missing: Cygnus applies damage by - * setting health directly, which never runs Minestom's damage pipeline and therefore never - * plays the sound a client would otherwise hear. - *
+ * The {@link #lobbyAtmosphereShare()} a configuration gets when it says nothing: enough of the + * map's haze to be recognised in the distance, while the lobby still reads as the lit room + * players wait in. */ - Key DEFAULT_DAMAGE_SOUND = Key.key("entity.player.hurt"); + public static final float DEFAULT_LOBBY_ATMOSPHERE_SHARE = 0.3F; /** - * The {@link #lobbyAtmosphereShare()} a configuration gets when it says nothing. + * The configuration used when there is no config file, or nothing in it can be read. *- * Enough of the map's own haze and colour to be recognised in the distance, far enough from it - * that the lobby still reads as the lit room players wait in rather than as the map itself. + * Sentry and the ResourcePack are opt-in, so a local run reports to nothing and pushes nothing. + * The hints and effects are on, since a round without them plays worse, not differently. *
*/ - float DEFAULT_LOBBY_ATMOSPHERE_SHARE = 0.3F; - - /** - * The static the slender hears while the survivors take his pages away. - *- * A resource pack sound rather than a vanilla one: three 2.2 second takes of tape hiss the - * client picks between, high-passed at 520 Hz so the effect's own pitch drop to 0.7 leaves - * it hissing rather than humming. Nothing in vanilla comes close - rain is the nearest, and - * it reads as weather. - *
- *- * A server running without the Cygnus pack therefore hears nothing here. That is the right - * way round: the static is a horror cue, and half of one played through the wrong sample is - * worse than none. - *
- */ - Key DEFAULT_SLENDER_STATIC_SOUND = Key.key("cygnus", "vhs_static"); - - /** The {@link #slenderStaticQuietInterval()} a configuration gets when it says nothing. */ - int DEFAULT_SLENDER_STATIC_QUIET_INTERVAL = 12; - - /** The {@link #slenderStaticFranticInterval()} a configuration gets when it says nothing. */ - int DEFAULT_SLENDER_STATIC_FRANTIC_INTERVAL = 3; - - /** - * The longest {@link #slenderStaticQuietInterval()} a configuration may ask for. Past this a - * round could end before the slender has heard the static twice, which makes it noise rather - * than a clock. - */ - int MAX_SLENDER_STATIC_INTERVAL = 120; - - /** The {@link #slenderStaticMinVolume()} a configuration gets when it says nothing. */ - float DEFAULT_SLENDER_STATIC_MIN_VOLUME = 0.15F; - - /** The {@link #slenderStaticMaxVolume()} a configuration gets when it says nothing. */ - float DEFAULT_SLENDER_STATIC_MAX_VOLUME = 0.8F; - - /** - * The largest {@link #glitchRange()} a configuration may ask for. Beyond this the slender would - * tear a survivor's view apart from across the map, which is the behaviour this range exists to - * end. - */ - int MAX_GLITCH_RANGE = 64; - - /** - * The widest {@link #glitchViewAngle()} a configuration may ask for. At 90 degrees and beyond - * the cone stops being a cone: everything not strictly behind the survivor would count as seen, - * and the effect would no longer be about looking at him. - */ - int MAX_GLITCH_VIEW_ANGLE = 89; - - /** - * The {@link #glitchRange()} a configuration gets when it says nothing. Twelve blocks is close - * enough that the slender is a present threat when the tearing starts - the 32 this used to be - * kept him inside the range for most of a round, so the effect was near enough permanent and - * stopped reading as a warning. - */ - int DEFAULT_GLITCH_RANGE = 12; - - /** The {@link #glitchCloseRange()} a configuration gets when it says nothing. */ - int DEFAULT_GLITCH_CLOSE_RANGE = 4; - - /** - * The {@link #glitchViewAngle()} a configuration gets when it says nothing. Narrower than the - * client's field of view on purpose: the cone this replaces spanned roughly 113 degrees and - * fired while he stood at the very edge of the screen, which is not the same as being looked at. - */ - int DEFAULT_GLITCH_VIEW_ANGLE = 30; - - /** - * Creates a new {@link Builder} which can be used to create a new game configuration. - * - * @return the builder instance - */ - @Contract(pure = true) - static Builder builder() { - return new GameConfigBuilder(); + public static final GameConfig DEFAULT = new GameConfig( + Round.DEFAULT, + Teams.DEFAULT, + null, + ResourcePack.NONE, + PageProximity.DEFAULT, + DamageSound.DEFAULT, + Glitch.DEFAULT, + SlenderStatic.DEFAULT, + DEFAULT_LOBBY_ATMOSPHERE_SHARE + ); + + public GameConfig { + if (lobbyAtmosphereShare < 0.0F || lobbyAtmosphereShare > 1.0F) { + throw new IllegalArgumentException("Lobby atmosphere share must be between 0 and 1"); + } } /** - * Returns the minimum number of players required to start a game. - * - * @return the minimum number of players - */ - int minPlayers(); - - /** - * Returns the maximum number of players allowed in the game. - * - * @return the maximum number of players - */ - int maxPlayers(); - - /** - * Returns the lobby time in seconds. - * - * @return the lobby time - */ - int lobbyTime(); - - /** - * Returns the maximum game time in seconds. - * - * @return the maximum game time - */ - int gameTime(); - - /** - * Returns the size of the slender team. - * - * @return the size of the slender team - */ - int slenderTeamSize(); - - /** - * Returns the size of the survivor team. - * - * @return the size of the survivor team - */ - int survivorTeamSize(); - - /** - * Returns the DSN which points Sentry at the project the errors of this service belong to. - *- * The Sentry integration is opt-in: without a DSN there is nothing to report to, so no client - * is set up at all. That is the expected state for local runs and tests. - *
+ * The player limits and timings of a round. * - * @return the configured DSN, or {@code null} when the Sentry integration stays off - * @since 2.11.0 + * @param minPlayers the number of players needed to start the countdown + * @param maxPlayers the number of players allowed in a round + * @param lobbyTime the countdown in seconds, longer than {@link #FORCE_START_TIME} + * @param gameTime the length of a round in seconds */ - @Nullable - String sentryDsn(); + public record Round(int minPlayers, int maxPlayers, int lobbyTime, int gameTime) { - /** - * Returns the location the client downloads the ResourcePack from. - * - * @return the configured URL, or {@code null} when the ResourcePack feature stays off - * @since 2.11.0 - */ - @Nullable - URI resourcePackUrl(); + public static final Round DEFAULT = new Round(2, 13, 30, 900); - /** - * Returns the SHA-1 checksum the client verifies the downloaded ResourcePack against. - *- * A production setup always states the checksum: it is what lets a client reuse the pack it - * already has instead of downloading it again on every join. Leaving it out is a test-only - * convenience - the checksum is then computed from the pack behind {@link #resourcePackUrl()} - * at runtime. - *
- * - * @return the configured checksum, or {@code null} when it has to be computed - * @since 2.11.0 - */ - @Nullable - String resourcePackSha1(); - - /** - * Returns whether survivors hear a sound while a page is nearby. - * - * @return {@code true} while the proximity hint is on - * @since 2.12.0 - */ - boolean pageProximityEnabled(); + public Round { + if (lobbyTime <= FORCE_START_TIME) { + throw new IllegalArgumentException("Lobby time must be greater than " + FORCE_START_TIME); + } + } + } /** - * Returns how far away a page may be and still be heard, in blocks. - *- * The value doubles as the volume the sound is played at: Minecraft carries a sound - * {@code 16 * volume} blocks, so a range beyond 16 blocks needs a volume above 1 to reach that - * far, and the server clips anything past the range itself. - *
+ * The sizes of the teams. * - * @return the range in blocks, at most {@link #MAX_PAGE_PROXIMITY_RANGE} - * @since 2.12.0 + * @param slenderSize the size of the slender team, at least 1 + * @param survivorSize the size of the survivor team, larger than the slender team's minimum */ - int pageProximityRange(); + public record Teams(int slenderSize, int survivorSize) { - /** - * Returns the number of ticks between two proximity sounds. - * - * @return the interval in ticks, at least 1 - * @since 2.12.0 - */ - int pageProximityInterval(); + private static final int MIN_SLENDER_SIZE = 1; - /** - * Returns the sound played while a page is nearby. - *- * The key is not resolved against the sound registry here - a key that names no known sound is - * only noticed when the sound is first played, and the proximity hint falls back to - * {@link #DEFAULT_PAGE_PROXIMITY_SOUND} then. - *
- * - * @return the sound key, never {@code null} - * @since 2.12.0 - */ - Key pageProximitySound(); + public static final Teams DEFAULT = new Teams(1, 12); - /** - * Returns how far past {@link #pageProximityRange()} the chime's falloff is stretched. - *- * Minecraft carries a sound {@code 16 * volume} blocks and fades it to nothing at that - * distance, so a volume derived to reach exactly the configured range leaves the chime - * inaudible at the range's edge. This factor stretches the falloff beyond it; the audible - * distance is unaffected, because the service drops pages outside the range before playing - * anything. - *
- * - * @return the factor, between 1 and {@link #MAX_PAGE_PROXIMITY_VOLUME_FACTOR} - * @since 2.12.1 - */ - float pageProximityVolumeFactor(); - - /** - * Returns whether a player hears a sound when they take damage. - * - * @return {@code true} while the damage feedback is on - * @since 2.13.0 - */ - boolean damageSoundEnabled(); + public Teams { + if (slenderSize < MIN_SLENDER_SIZE) { + throw new IllegalArgumentException("Slender team size must be at least " + MIN_SLENDER_SIZE); + } + if (survivorSize < MIN_SLENDER_SIZE + 1) { + throw new IllegalArgumentException("Survivor team size must be at least " + (MIN_SLENDER_SIZE + 1)); + } + } + } /** - * Returns how many ticks have to pass before a player hears the damage sound again. - *- * The slender damages everyone around him twice a second for as long as he drains, so without - * a cooldown a survivor standing next to him would hear the sound at that rate. - *
+ * Where the client gets the ResourcePack from. * - * @return the cooldown in ticks, at least 1 - * @since 2.13.0 + * @param url the location of the pack, or {@code null} to keep the feature off + * @param sha1 the checksum the client verifies the pack against, or {@code null} to compute it + * from the pack at runtime. A production setup always states it: it lets a client + * reuse the pack it already has instead of downloading it on every join. */ - int damageSoundCooldown(); + public record ResourcePack(@Nullable URI url, @Nullable String sha1) { - /** - * Returns the sound played to a player who was just hit. - *- * The key is not resolved against the sound registry here - a key that names no known sound is - * only noticed when the sound is first played, and the feedback falls back to - * {@link #DEFAULT_DAMAGE_SOUND} then. - *
- * - * @return the sound key, never {@code null} - * @since 2.13.0 - */ - Key damageSound(); + public static final ResourcePack NONE = new ResourcePack(null, null); + } /** - * Returns how far the lobby's atmosphere is taken from the open end towards the game map's own. - *- * {@code 0} leaves the lobby on the vanilla overworld, which is where it was. {@code 1} gives it - * exactly the map's atmosphere, which makes the start of a round invisible - the point of the - * setting is the distance between the two, so that walking into the round reads as the world - * closing in rather than as a cut. - *
+ * The sound survivors hear while a page is nearby. * - * @return the share, between 0 and 1 - * @since 2.14.0 + * @param enabled whether the hint is played at all + * @param range how far away a page may be and still be heard, in blocks + * @param sound the sound; a key naming no known sound falls back to {@link #DEFAULT_SOUND} + * when it is first played + * @param volumeFactor how far past the range the falloff is stretched. Minecraft fades a sound to + * nothing at {@code 16 * volume} blocks, so a volume that reaches exactly the + * range would be silent at its edge. */ - float lobbyAtmosphereShare(); + public record PageProximity(boolean enabled, int range, Key sound, float volumeFactor) { - /** - * Returns whether the slender hears static as the survivors collect his pages. - * - * @return {@code true} while the static is on - * @since 2.14.0 - */ - boolean slenderStaticEnabled(); + /** A soft, bell-less shimmer that reads as "something is here" without sounding like an alarm. */ + public static final Key DEFAULT_SOUND = Key.key("block.amethyst_block.chime"); - /** - * Returns the sound the static is built from. - *- * The key is not resolved against the sound registry here: a resource pack sound is a perfectly - * good answer and would not be found in it. It is sent as named. - *
- * - * @return the sound key, never {@code null} - * @since 2.14.0 - */ - Key slenderStaticSound(); + /** Beyond this a single page would be audible across a good part of the map. */ + public static final int MAX_RANGE = 64; - /** - * Returns how many seconds lie between two bursts while no page has been found. - * - * @return the interval in seconds, at most {@link #MAX_SLENDER_STATIC_INTERVAL} - * @since 2.14.0 - */ - int slenderStaticQuietInterval(); + /** Leaves roughly half the volume at the edge of the range. */ + public static final float DEFAULT_VOLUME_FACTOR = 2.0F; - /** - * Returns how many seconds lie between two bursts once every page is gone. - *- * The gap shrinks from {@link #slenderStaticQuietInterval()} towards this value as the pages - * disappear, which is what tells the slender how late in the round he is. - *
- * - * @return the interval in seconds, below {@link #slenderStaticQuietInterval()} - * @since 2.14.0 - */ - int slenderStaticFranticInterval(); + /** + * Past this the chime is evenly loud across the whole range, so a player can no longer tell a + * page two blocks away from one at the edge. + */ + public static final float MAX_VOLUME_FACTOR = 8.0F; + + public static final PageProximity DEFAULT = new PageProximity(true, 20, DEFAULT_SOUND, DEFAULT_VOLUME_FACTOR); + + public PageProximity { + if (range < 1 || range > MAX_RANGE) { + throw new IllegalArgumentException("Page proximity range must be between 1 and " + MAX_RANGE); + } + if (volumeFactor < 1.0F || volumeFactor > MAX_VOLUME_FACTOR) { + throw new IllegalArgumentException("Page proximity volume factor must be between 1 and " + MAX_VOLUME_FACTOR); + } + } + } /** - * Returns how loud the static is while no page has been found. + * The sound a player hears when hit. * - * @return the volume, between 0 and {@link #slenderStaticMaxVolume()} - * @since 2.14.0 + * @param enabled whether the sound is played at all + * @param cooldown the ticks before a player hears it again, at least 1. The slender damages + * everyone around him twice a second while he drains. + * @param sound the sound; a key naming no known sound falls back to {@link #DEFAULT_SOUND} + * when it is first played */ - float slenderStaticMinVolume(); + public record DamageSound(boolean enabled, int cooldown, Key sound) { - /** - * Returns how loud the static is once every page is gone. - * - * @return the volume, at most 1 - * @since 2.14.0 - */ - float slenderStaticMaxVolume(); + /** + * The vanilla hurt sound: Cygnus sets health directly, which never runs Minestom's damage + * pipeline and so never plays the sound a client would otherwise hear. + */ + public static final Key DEFAULT_SOUND = Key.key("entity.player.hurt"); - /** - * Returns how close the slender has to be before the sight of him tears a survivor's view. - *- * This is the outer edge of the effect, not the point where it is strongest: at exactly this - * distance a survivor gets the weakest level, and it grows the nearer he comes until - * {@link #glitchCloseRange()} is reached. Beyond it there is nothing at all - no veil, and no - * darkening of the world. - *
- * - * @return the range in blocks, at most {@link #MAX_GLITCH_RANGE} - * @since 2.13.0 - */ - int glitchRange(); + /** Lets through every second damage tick of a draining slender: enough to notice, not enough to grate. */ + public static final DamageSound DEFAULT = new DamageSound(true, 20, DEFAULT_SOUND); - /** - * Returns the distance at which the tearing is at its worst. - *- * Always smaller than {@link #glitchRange()} - the two mark the ends of the same slope, and a - * configuration where they meet or cross is rejected outright. - *
- * - * @return the distance in blocks, at least 1 and below {@link #glitchRange()} - * @since 2.13.0 - */ - int glitchCloseRange(); + public DamageSound { + if (cooldown < 1) { + throw new IllegalArgumentException("Damage sound cooldown must be at least 1 tick"); + } + } + } /** - * Returns how far off the centre of their view the slender may stand and still count as seen. - *- * Given in degrees around the survivor's line of sight, so a value of 30 means he has to be - * within 30 degrees of where they are actually looking. This is deliberately narrower than the - * client's field of view: standing at the very edge of the screen is not the same as being - * looked at. - *
- * - * @return the half-angle in degrees, between 1 and {@link #MAX_GLITCH_VIEW_ANGLE} - * @since 2.13.0 - */ - int glitchViewAngle(); + * How the sight of the slender tears a survivor's view. + * + * @param range the outer edge of the effect in blocks; the level grows from here towards the + * close range, and beyond it there is nothing at all + * @param closeRange the distance at which the tearing is at its worst, below the range + * @param viewAngle how far off the centre of the view the slender may stand and still count as + * seen, in degrees. Narrower than the client's field of view on purpose: the edge + * of the screen is not the same as being looked at. + */ + public record Glitch(int range, int closeRange, int viewAngle) { + + /** Beyond this the slender would tear a survivor's view apart from across the map. */ + public static final int MAX_RANGE = 64; + + /** At 90 degrees and beyond everything not strictly behind the survivor would count as seen. */ + public static final int MAX_VIEW_ANGLE = 89; + + /** Twelve blocks keeps the effect a warning; the 32 this used to be made it near enough permanent. */ + public static final Glitch DEFAULT = new Glitch(12, 4, 30); + + public Glitch { + if (range < 1 || range > MAX_RANGE) { + throw new IllegalArgumentException("Glitch range must be between 1 and " + MAX_RANGE); + } + if (closeRange < 1) { + throw new IllegalArgumentException("Glitch close range must be at least 1 block"); + } + if (viewAngle < 1 || viewAngle > MAX_VIEW_ANGLE) { + throw new IllegalArgumentException("Glitch view angle must be between 1 and " + MAX_VIEW_ANGLE + " degrees"); + } + if (closeRange >= range) { + throw new IllegalArgumentException( + "Glitch close range (" + closeRange + ") must be below the glitch range (" + range + ")"); + } + } + } /** - * The {@link Builder} interface is used to create a new game configuration. - * It provides methods to set the values for the configuration. + * The static the slender hears while the survivors take his pages away. It is the only thing + * that tells him how far they have got without putting the page counter in front of him. * - * @author theEvilReaper - * @version 1.0.0 - * @since 1.0.0 + * @param enabled whether the static is played at all + * @param sound the sound, sent as named: a resource pack sound would not be found in the + * registry + * @param quietInterval the seconds between two bursts while no page has been found + * @param franticInterval the seconds between two bursts once every page is gone, below the quiet + * interval + * @param minVolume the volume while no page has been found + * @param maxVolume the volume once every page is gone */ - sealed interface Builder permits GameConfigBuilder { - - /** - * Sets the minimum number of players required to start a game. - * - * @param minPlayers the minimum number of players - * @return the builder instance - */ - Builder minPlayers(int minPlayers); - - /** - * Sets the maximum number of players allowed in the game. - * - * @param maxPlayers the maximum number of players - * @return the builder instance - */ - Builder maxPlayers(int maxPlayers); - - /** - * Sets the lobby time in seconds. - * - * @param lobbyTime the lobby time - * @return the builder instance - * @throws IllegalArgumentException if the lobby time is than the {@link GameConfig#FORCE_START_TIME} - */ - Builder lobbyTime(int lobbyTime); - - /** - * Sets the maximum game time in seconds. - * - * @param gameTime the maximum game time - * @return the builder instance - */ - Builder gameTime(int gameTime); - - /** - * Sets the size of the slender team. - * - * @param slenderTeamSize the size of the slender team - * @return the builder instance - * @throws IllegalArgumentException if the slender team size is smaller than 1 - */ - Builder slenderTeamSize(int slenderTeamSize); - - /** - * Sets the size of the survivor team. - * - * @param survivorTeamSize the size of the survivor team - * @return the builder instance - * @throws IllegalArgumentException if the survivor team size is smaller than 1 - */ - Builder survivorTeamSize(int survivorTeamSize); - - /** - * Sets the DSN which points Sentry at the project the errors of this service belong to. - * - * @param sentryDsn the DSN, or {@code null} to leave the Sentry integration off - * @return the builder instance - * @since 2.11.0 - */ - Builder sentryDsn(@Nullable String sentryDsn); - - /** - * Sets the location the client downloads the ResourcePack from. - * - * @param resourcePackUrl the URL, or {@code null} to leave the ResourcePack feature off - * @return the builder instance - * @since 2.11.0 - */ - Builder resourcePackUrl(@Nullable URI resourcePackUrl); - - /** - * Sets the SHA-1 checksum the client verifies the downloaded ResourcePack against. - * - * @param resourcePackSha1 the checksum, or {@code null} to have it computed at runtime - * @return the builder instance - * @since 2.11.0 - */ - Builder resourcePackSha1(@Nullable String resourcePackSha1); - - /** - * Sets whether survivors hear a sound while a page is nearby. - * - * @param pageProximityEnabled {@code true} to keep the proximity hint on - * @return the builder instance - * @since 2.12.0 - */ - Builder pageProximityEnabled(boolean pageProximityEnabled); - - /** - * Sets how far away a page may be and still be heard, in blocks. - * - * @param pageProximityRange the range in blocks - * @return the builder instance - * @throws IllegalArgumentException if the range is below 1 or above - * {@link GameConfig#MAX_PAGE_PROXIMITY_RANGE} - * @since 2.12.0 - */ - Builder pageProximityRange(int pageProximityRange); - - /** - * Sets the number of ticks between two proximity sounds. - * - * @param pageProximityInterval the interval in ticks - * @return the builder instance - * @throws IllegalArgumentException if the interval is below 1 - * @since 2.12.0 - */ - Builder pageProximityInterval(int pageProximityInterval); - - /** - * Sets the sound played while a page is nearby. - * - * @param pageProximitySound the sound key - * @return the builder instance - * @since 2.12.0 - */ - Builder pageProximitySound(Key pageProximitySound); - - /** - * Sets how far past the range the chime's falloff is stretched. - * - * @param pageProximityVolumeFactor the factor - * @return the builder instance - * @throws IllegalArgumentException if the factor is below 1 or above - * {@link GameConfig#MAX_PAGE_PROXIMITY_VOLUME_FACTOR} - * @since 2.12.1 - */ - Builder pageProximityVolumeFactor(float pageProximityVolumeFactor); - - /** - * Sets whether a player hears a sound when they take damage. - * - * @param damageSoundEnabled {@code true} to keep the damage feedback on - * @return the builder instance - * @since 2.13.0 - */ - Builder damageSoundEnabled(boolean damageSoundEnabled); - - /** - * Sets how many ticks have to pass before a player hears the damage sound again. - * - * @param damageSoundCooldown the cooldown in ticks - * @return the builder instance - * @throws IllegalArgumentException if the cooldown is below 1 - * @since 2.13.0 - */ - Builder damageSoundCooldown(int damageSoundCooldown); - - /** - * Sets the sound played to a player who was just hit. - * - * @param damageSound the sound key - * @return the builder instance - * @since 2.13.0 - */ - Builder damageSound(Key damageSound); - - /** - * Sets how far the lobby's atmosphere is taken towards the game map's own. - * - * @param lobbyAtmosphereShare the share - * @return the builder instance - * @throws IllegalArgumentException if the share is below 0 or above 1 - * @since 2.14.0 - */ - Builder lobbyAtmosphereShare(float lobbyAtmosphereShare); - - /** - * Sets whether the slender hears static as the survivors collect his pages. - * - * @param slenderStaticEnabled {@code true} to keep the static on - * @return the builder instance - * @since 2.14.0 - */ - Builder slenderStaticEnabled(boolean slenderStaticEnabled); - - /** - * Sets the sound the static is built from. - * - * @param slenderStaticSound the sound key - * @return the builder instance - * @since 2.14.0 - */ - Builder slenderStaticSound(Key slenderStaticSound); - - /** - * Sets how many seconds lie between two bursts while no page has been found. - * - * @param slenderStaticQuietInterval the interval in seconds - * @return the builder instance - * @throws IllegalArgumentException if the interval is below 1 or above - * {@link GameConfig#MAX_SLENDER_STATIC_INTERVAL} - * @since 2.14.0 - */ - Builder slenderStaticQuietInterval(int slenderStaticQuietInterval); - - /** - * Sets how many seconds lie between two bursts once every page is gone. - * - * @param slenderStaticFranticInterval the interval in seconds - * @return the builder instance - * @throws IllegalArgumentException if the interval is below 1 - * @since 2.14.0 - */ - Builder slenderStaticFranticInterval(int slenderStaticFranticInterval); - - /** - * Sets how loud the static is while no page has been found. - * - * @param slenderStaticMinVolume the volume - * @return the builder instance - * @throws IllegalArgumentException if the volume is below 0 or above 1 - * @since 2.14.0 - */ - Builder slenderStaticMinVolume(float slenderStaticMinVolume); - - /** - * Sets how loud the static is once every page is gone. - * - * @param slenderStaticMaxVolume the volume - * @return the builder instance - * @throws IllegalArgumentException if the volume is below 0 or above 1 - * @since 2.14.0 - */ - Builder slenderStaticMaxVolume(float slenderStaticMaxVolume); - - /** - * Sets how close the slender has to be before the sight of him tears a survivor's view. - * - * @param glitchRange the range in blocks - * @return the builder instance - * @throws IllegalArgumentException if the range is below 1 or above - * {@link GameConfig#MAX_GLITCH_RANGE} - * @since 2.13.0 - */ - Builder glitchRange(int glitchRange); - - /** - * Sets the distance at which the tearing is at its worst. - * - * @param glitchCloseRange the distance in blocks - * @return the builder instance - * @throws IllegalArgumentException if the distance is below 1 - * @since 2.13.0 - */ - Builder glitchCloseRange(int glitchCloseRange); - - /** - * Sets how far off the centre of their view the slender may stand and still count as seen. - * - * @param glitchViewAngle the half-angle in degrees - * @return the builder instance - * @throws IllegalArgumentException if the angle is below 1 or above - * {@link GameConfig#MAX_GLITCH_VIEW_ANGLE} - * @since 2.13.0 - */ - Builder glitchViewAngle(int glitchViewAngle); + public record SlenderStatic( + boolean enabled, + Key sound, + int quietInterval, + int franticInterval, + float minVolume, + float maxVolume + ) { /** - * Builds the game configuration. - * - * @return the created configuration + * Three takes of tape hiss from the Cygnus resource pack. A server without the pack hears + * nothing here, which beats half a horror cue played through the wrong sample. */ - GameConfig build(); + public static final Key DEFAULT_SOUND = Key.key("cygnus", "vhs_static"); + + /** Past this a round could end before the slender has heard the static twice. */ + public static final int MAX_INTERVAL = 120; + + public static final SlenderStatic DEFAULT = new SlenderStatic(true, DEFAULT_SOUND, 12, 3, 0.15F, 0.8F); + + public SlenderStatic { + if (quietInterval < 1 || quietInterval > MAX_INTERVAL) { + throw new IllegalArgumentException( + "Slender static quiet interval must be between 1 and " + MAX_INTERVAL + " seconds"); + } + if (franticInterval < 1) { + throw new IllegalArgumentException("Slender static frantic interval must be at least 1 second"); + } + checkVolume(minVolume, "minimum"); + checkVolume(maxVolume, "maximum"); + if (franticInterval >= quietInterval) { + throw new IllegalArgumentException( + "Slender static frantic interval (" + franticInterval + + ") must be below the quiet interval (" + quietInterval + ")"); + } + if (minVolume > maxVolume) { + throw new IllegalArgumentException( + "Slender static minimum volume (" + minVolume + + ") must not be above the maximum volume (" + maxVolume + ")"); + } + } + + private static void checkVolume(float volume, String name) { + if (volume < 0.0F || volume > 1.0F) { + throw new IllegalArgumentException("Slender static " + name + " volume must be between 0 and 1"); + } + } } } diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigBuilder.java b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigBuilder.java deleted file mode 100644 index a799787f..00000000 --- a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigBuilder.java +++ /dev/null @@ -1,338 +0,0 @@ -package net.onelitefeather.cygnus.common.config; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.Nullable; - -import java.net.URI; - -/** - * The {@link GameConfigBuilder} is the implementation of the {@link GameConfig.Builder} interface. - * It collects the values for a game configuration and creates a {@link GameConfigImpl} from them. - * - * @author theEvilReaper - * @version 1.2.0 - * @since 1.0.0 - */ -public final class GameConfigBuilder implements GameConfig.Builder { - - private int minPlayers; - private int maxPlayers; - private int lobbyTime; - private int maxGameTime; - private int slenderTeamSize; - private int survivorTeamSize; - private @Nullable String sentryDsn; - private @Nullable URI resourcePackUrl; - private @Nullable String resourcePackSha1; - private boolean pageProximityEnabled; - private int pageProximityRange; - private int pageProximityInterval; - private Key pageProximitySound = GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND; - private float pageProximityVolumeFactor = GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR; - private boolean damageSoundEnabled; - private int damageSoundCooldown; - private Key damageSound = GameConfig.DEFAULT_DAMAGE_SOUND; - // Pre-set rather than left at zero, the way pageProximitySound above is. build() checks the - // two distances against each other, and a builder used directly - as the tests do - would trip - // that check on 0 >= 0 without ever having said anything about the gaze. - private int glitchRange = GameConfig.DEFAULT_GLITCH_RANGE; - private int glitchCloseRange = GameConfig.DEFAULT_GLITCH_CLOSE_RANGE; - private int glitchViewAngle = GameConfig.DEFAULT_GLITCH_VIEW_ANGLE; - private float lobbyAtmosphereShare = GameConfig.DEFAULT_LOBBY_ATMOSPHERE_SHARE; - private boolean slenderStaticEnabled; - private Key slenderStaticSound = GameConfig.DEFAULT_SLENDER_STATIC_SOUND; - // Pre-set for the same reason as the glitch distances above: build() checks the two intervals - // and the two volumes against each other, and a builder that was never told about the static - // would trip those checks on a pair of zeroes. - private int slenderStaticQuietInterval = GameConfig.DEFAULT_SLENDER_STATIC_QUIET_INTERVAL; - private int slenderStaticFranticInterval = GameConfig.DEFAULT_SLENDER_STATIC_FRANTIC_INTERVAL; - private float slenderStaticMinVolume = GameConfig.DEFAULT_SLENDER_STATIC_MIN_VOLUME; - private float slenderStaticMaxVolume = GameConfig.DEFAULT_SLENDER_STATIC_MAX_VOLUME; - - @Override - public GameConfig.Builder minPlayers(int minPlayers) { - this.minPlayers = minPlayers; - return this; - } - - @Override - public GameConfig.Builder maxPlayers(int maxPlayers) { - this.maxPlayers = maxPlayers; - return this; - } - - @Override - public GameConfig.Builder lobbyTime(int lobbyTime) { - if (lobbyTime <= GameConfig.FORCE_START_TIME) { - throw new IllegalArgumentException("Lobby time must be greater than " + GameConfig.FORCE_START_TIME); - } - this.lobbyTime = lobbyTime; - return this; - } - - @Override - public GameConfig.Builder gameTime(int gameTime) { - this.maxGameTime = gameTime; - return this; - } - - @Override - public GameConfig.Builder slenderTeamSize(int slenderTeamSize) { - int minSlenderSize = InternalGameConfig.defaultConfig().slenderTeamSize(); - if (slenderTeamSize < minSlenderSize) { - throw new IllegalArgumentException("Slender team size must be at least " + minSlenderSize); - } - this.slenderTeamSize = slenderTeamSize; - return this; - } - - @Override - public GameConfig.Builder survivorTeamSize(int survivorTeamSize) { - int minSurvivorSize = InternalGameConfig.defaultConfig().slenderTeamSize() + 1; - if (survivorTeamSize < minSurvivorSize) { - throw new IllegalArgumentException("Survivor team size must be at least " + minSurvivorSize); - } - this.survivorTeamSize = survivorTeamSize; - return this; - } - - @Override - public GameConfig.Builder sentryDsn(@Nullable String sentryDsn) { - this.sentryDsn = sentryDsn; - return this; - } - - @Override - public GameConfig.Builder resourcePackUrl(@Nullable URI resourcePackUrl) { - this.resourcePackUrl = resourcePackUrl; - return this; - } - - @Override - public GameConfig.Builder resourcePackSha1(@Nullable String resourcePackSha1) { - this.resourcePackSha1 = resourcePackSha1; - return this; - } - - @Override - public GameConfig.Builder pageProximityEnabled(boolean pageProximityEnabled) { - this.pageProximityEnabled = pageProximityEnabled; - return this; - } - - @Override - public GameConfig.Builder pageProximityRange(int pageProximityRange) { - if (pageProximityRange < 1 || pageProximityRange > GameConfig.MAX_PAGE_PROXIMITY_RANGE) { - throw new IllegalArgumentException( - "Page proximity range must be between 1 and " + GameConfig.MAX_PAGE_PROXIMITY_RANGE); - } - this.pageProximityRange = pageProximityRange; - return this; - } - - @Override - public GameConfig.Builder pageProximityInterval(int pageProximityInterval) { - if (pageProximityInterval < 1) { - throw new IllegalArgumentException("Page proximity interval must be at least 1 tick"); - } - this.pageProximityInterval = pageProximityInterval; - return this; - } - - @Override - public GameConfig.Builder pageProximitySound(Key pageProximitySound) { - this.pageProximitySound = pageProximitySound; - return this; - } - - @Override - public GameConfig.Builder pageProximityVolumeFactor(float pageProximityVolumeFactor) { - if (pageProximityVolumeFactor < 1.0F - || pageProximityVolumeFactor > GameConfig.MAX_PAGE_PROXIMITY_VOLUME_FACTOR) { - throw new IllegalArgumentException( - "Page proximity volume factor must be between 1 and " - + GameConfig.MAX_PAGE_PROXIMITY_VOLUME_FACTOR); - } - this.pageProximityVolumeFactor = pageProximityVolumeFactor; - return this; - } - - @Override - public GameConfig.Builder damageSoundEnabled(boolean damageSoundEnabled) { - this.damageSoundEnabled = damageSoundEnabled; - return this; - } - - @Override - public GameConfig.Builder damageSoundCooldown(int damageSoundCooldown) { - if (damageSoundCooldown < 1) { - throw new IllegalArgumentException("Damage sound cooldown must be at least 1 tick"); - } - this.damageSoundCooldown = damageSoundCooldown; - return this; - } - - @Override - public GameConfig.Builder damageSound(Key damageSound) { - this.damageSound = damageSound; - return this; - } - - @Override - public GameConfig.Builder glitchRange(int glitchRange) { - if (glitchRange < 1 || glitchRange > GameConfig.MAX_GLITCH_RANGE) { - throw new IllegalArgumentException( - "Glitch range must be between 1 and " + GameConfig.MAX_GLITCH_RANGE); - } - this.glitchRange = glitchRange; - return this; - } - - @Override - public GameConfig.Builder glitchCloseRange(int glitchCloseRange) { - if (glitchCloseRange < 1) { - throw new IllegalArgumentException("Glitch close range must be at least 1 block"); - } - this.glitchCloseRange = glitchCloseRange; - return this; - } - - @Override - public GameConfig.Builder glitchViewAngle(int glitchViewAngle) { - if (glitchViewAngle < 1 || glitchViewAngle > GameConfig.MAX_GLITCH_VIEW_ANGLE) { - throw new IllegalArgumentException( - "Glitch view angle must be between 1 and " + GameConfig.MAX_GLITCH_VIEW_ANGLE + " degrees"); - } - this.glitchViewAngle = glitchViewAngle; - return this; - } - - @Override - public GameConfig.Builder lobbyAtmosphereShare(float lobbyAtmosphereShare) { - if (lobbyAtmosphereShare < 0.0F || lobbyAtmosphereShare > 1.0F) { - throw new IllegalArgumentException("Lobby atmosphere share must be between 0 and 1"); - } - this.lobbyAtmosphereShare = lobbyAtmosphereShare; - return this; - } - - @Override - public GameConfig.Builder slenderStaticEnabled(boolean slenderStaticEnabled) { - this.slenderStaticEnabled = slenderStaticEnabled; - return this; - } - - @Override - public GameConfig.Builder slenderStaticSound(Key slenderStaticSound) { - this.slenderStaticSound = slenderStaticSound; - return this; - } - - @Override - public GameConfig.Builder slenderStaticQuietInterval(int slenderStaticQuietInterval) { - if (slenderStaticQuietInterval < 1 - || slenderStaticQuietInterval > GameConfig.MAX_SLENDER_STATIC_INTERVAL) { - throw new IllegalArgumentException( - "Slender static quiet interval must be between 1 and " - + GameConfig.MAX_SLENDER_STATIC_INTERVAL + " seconds"); - } - this.slenderStaticQuietInterval = slenderStaticQuietInterval; - return this; - } - - @Override - public GameConfig.Builder slenderStaticFranticInterval(int slenderStaticFranticInterval) { - if (slenderStaticFranticInterval < 1) { - throw new IllegalArgumentException("Slender static frantic interval must be at least 1 second"); - } - this.slenderStaticFranticInterval = slenderStaticFranticInterval; - return this; - } - - @Override - public GameConfig.Builder slenderStaticMinVolume(float slenderStaticMinVolume) { - this.slenderStaticMinVolume = checkVolume(slenderStaticMinVolume, "minimum"); - return this; - } - - @Override - public GameConfig.Builder slenderStaticMaxVolume(float slenderStaticMaxVolume) { - this.slenderStaticMaxVolume = checkVolume(slenderStaticMaxVolume, "maximum"); - return this; - } - - /** - * Checks a static volume against the range a sound can carry. - * - * @param volume the volume to check - * @param name how the volume is named in the message of a failure - * @return the volume - * @throws IllegalArgumentException if the volume is outside 0 to 1 - */ - private static float checkVolume(float volume, String name) { - if (volume < 0.0F || volume > 1.0F) { - throw new IllegalArgumentException( - "Slender static " + name + " volume must be between 0 and 1"); - } - return volume; - } - - /** - * {@inheritDoc} - *- * The two glitch distances are checked against each other here rather than in their setters. - * Each one alone is a valid number; only together do they say whether the slope they describe - * runs the right way, and a setter cannot know that - it would depend on which of the two was - * called first. - *
- * - * @throws IllegalArgumentException if the close range is not below the range - */ - @Override - public GameConfig build() { - if (glitchCloseRange >= glitchRange) { - throw new IllegalArgumentException( - "Glitch close range (" + glitchCloseRange + ") must be below the glitch range (" - + glitchRange + ")"); - } - if (slenderStaticFranticInterval >= slenderStaticQuietInterval) { - throw new IllegalArgumentException( - "Slender static frantic interval (" + slenderStaticFranticInterval - + ") must be below the quiet interval (" + slenderStaticQuietInterval + ")"); - } - if (slenderStaticMinVolume > slenderStaticMaxVolume) { - throw new IllegalArgumentException( - "Slender static minimum volume (" + slenderStaticMinVolume - + ") must not be above the maximum volume (" + slenderStaticMaxVolume + ")"); - } - return new GameConfigImpl( - minPlayers, - maxPlayers, - lobbyTime, - maxGameTime, - slenderTeamSize, - survivorTeamSize, - sentryDsn, - resourcePackUrl, - resourcePackSha1, - pageProximityEnabled, - pageProximityRange, - pageProximityInterval, - pageProximitySound, - pageProximityVolumeFactor, - damageSoundEnabled, - damageSoundCooldown, - damageSound, - glitchRange, - glitchCloseRange, - glitchViewAngle, - slenderStaticEnabled, - slenderStaticSound, - slenderStaticQuietInterval, - slenderStaticFranticInterval, - slenderStaticMinVolume, - slenderStaticMaxVolume, - lobbyAtmosphereShare - ); - } -} diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigImpl.java b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigImpl.java deleted file mode 100644 index e49dfdc6..00000000 --- a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.onelitefeather.cygnus.common.config; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.Nullable; - -import java.net.URI; - -/** - * The {@link GameConfigImpl} is the implementation of the {@link GameConfig} interface. - * It represents a configuration which is used to adjust some settings for the game. - * The configuration is immutable and can't be changed after the creation. - * - * @param minPlayers the minimum number of players required to start a game - * @param maxPlayers the maximum number of players allowed in the game - * @param lobbyTime the time in seconds before the game starts - * @param gameTime the maximum duration of a game in seconds - * @param slenderTeamSize the size of the slender team - * @param survivorTeamSize the size of the survivor team - * @param sentryDsn the DSN to report errors to, or {@code null} to keep Sentry off - * @param resourcePackUrl the location the client downloads the ResourcePack from, or {@code null} - * to keep the ResourcePack feature off - * @param resourcePackSha1 the checksum of the ResourcePack, or {@code null} to have it computed - * @param pageProximityEnabled whether survivors hear a sound while a page is nearby - * @param pageProximityRange how far away a page may be and still be heard, in blocks - * @param pageProximityInterval the number of ticks between two proximity sounds - * @param pageProximitySound the sound played while a page is nearby - * @param pageProximityVolumeFactor how far past the range the chime's falloff is stretched - * @param damageSoundEnabled whether a player hears a sound when they take damage - * @param damageSoundCooldown the number of ticks before the damage sound is played again - * @param damageSound the sound played to a player who was just hit - * @param glitchRange how close the slender has to be before the sight of him tears a - * survivor's view, in blocks - * @param glitchCloseRange the distance in blocks at which the tearing is at its worst - * @param glitchViewAngle how far off the centre of their view he may stand and still count - * as seen, in degrees - * @param lobbyAtmosphereShare how far the lobby's atmosphere is taken towards the map's own - * @param slenderStaticEnabled whether the slender hears static as his pages are collected - * @param slenderStaticSound the sound the static is built from - * @param slenderStaticQuietInterval the seconds between two bursts while no page has been found - * @param slenderStaticFranticInterval the seconds between two bursts once every page is gone - * @param slenderStaticMinVolume how loud the static is while no page has been found - * @param slenderStaticMaxVolume how loud the static is once every page is gone - * @author theEvilReaper - * @version 1.4.0 - * @since 1.0.0 - */ -public record GameConfigImpl( - int minPlayers, - int maxPlayers, - int lobbyTime, - int gameTime, - int slenderTeamSize, - int survivorTeamSize, - @Nullable String sentryDsn, - @Nullable URI resourcePackUrl, - @Nullable String resourcePackSha1, - boolean pageProximityEnabled, - int pageProximityRange, - int pageProximityInterval, - Key pageProximitySound, - float pageProximityVolumeFactor, - boolean damageSoundEnabled, - int damageSoundCooldown, - Key damageSound, - int glitchRange, - int glitchCloseRange, - int glitchViewAngle, - boolean slenderStaticEnabled, - Key slenderStaticSound, - int slenderStaticQuietInterval, - int slenderStaticFranticInterval, - float slenderStaticMinVolume, - float slenderStaticMaxVolume, - float lobbyAtmosphereShare -) implements GameConfig { - -} diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigReader.java b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigReader.java index 66824798..d9bc692b 100644 --- a/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigReader.java +++ b/common/src/main/java/net/onelitefeather/cygnus/common/config/GameConfigReader.java @@ -31,7 +31,6 @@ *
* If a property can not be found in the file, the default value will be used.
- * The default values are defined in the {@link InternalGameConfig} class.
+ * The default values are defined in {@link GameConfig#DEFAULT}.
*
* @author theEvilReaper
* @version 1.4.0
@@ -89,55 +88,67 @@ public GameConfigReader(Path path) {
public GameConfig getConfig() {
if (!Files.exists(path)) {
CONFIG_LOGGER.warn("No config file found. Using default values");
- return InternalGameConfig.defaultConfig();
+ return GameConfig.DEFAULT;
}
Properties properties = new Properties();
-
try (InputStream stream = Files.newInputStream(path)) {
properties.load(stream);
} catch (Exception exception) {
CONFIG_LOGGER.error("Failed to load config file", exception);
- return InternalGameConfig.defaultConfig();
+ return GameConfig.DEFAULT;
}
if (properties.isEmpty()) {
CONFIG_LOGGER.warn("Found config file but it is empty. Falling back to default values");
- return InternalGameConfig.defaultConfig();
+ return GameConfig.DEFAULT;
}
- GameConfig internal = InternalGameConfig.defaultConfig();
- GameConfig.Builder configBuilder = GameConfig.builder();
-
- configBuilder.minPlayers(getInt(properties, "minPlayers", internal.minPlayers()))
- .maxPlayers(getInt(properties, "maxPlayers", internal.maxPlayers()))
- .lobbyTime(getInt(properties, "lobbyTime", internal.lobbyTime()))
- .gameTime(getInt(properties, "gameTime", internal.gameTime()))
- .survivorTeamSize(getInt(properties, "survivorTeamSize", internal.survivorTeamSize()))
- .slenderTeamSize(getInt(properties, "slenderTeamSize", internal.slenderTeamSize()))
- .sentryDsn(getString(properties, SENTRY_DSN_KEY))
- .resourcePackUrl(getResourcePackUrl(properties))
- .resourcePackSha1(getResourcePackSha1(properties))
- .pageProximityEnabled(getBoolean(properties, "pageProximityEnabled", internal.pageProximityEnabled()))
- .pageProximityRange(getInt(properties, "pageProximityRange", internal.pageProximityRange()))
- .pageProximityInterval(getInt(properties, "pageProximityInterval", internal.pageProximityInterval()))
- .pageProximitySound(getSound(properties, PAGE_PROXIMITY_SOUND_KEY, internal.pageProximitySound()))
- .pageProximityVolumeFactor(getFloat(properties, "pageProximityVolumeFactor", internal.pageProximityVolumeFactor()))
- .damageSoundEnabled(getBoolean(properties, "damageSoundEnabled", internal.damageSoundEnabled()))
- .damageSoundCooldown(getInt(properties, "damageSoundCooldown", internal.damageSoundCooldown()))
- .damageSound(getSound(properties, DAMAGE_SOUND_KEY, internal.damageSound()))
- .glitchRange(getInt(properties, "glitchRange", internal.glitchRange()))
- .glitchCloseRange(getInt(properties, "glitchCloseRange", internal.glitchCloseRange()))
- .glitchViewAngle(getInt(properties, "glitchViewAngle", internal.glitchViewAngle()))
- .slenderStaticEnabled(getBoolean(properties, "slenderStaticEnabled", internal.slenderStaticEnabled()))
- .slenderStaticSound(getSound(properties, SLENDER_STATIC_SOUND_KEY, internal.slenderStaticSound()))
- .slenderStaticQuietInterval(getInt(properties, "slenderStaticQuietInterval", internal.slenderStaticQuietInterval()))
- .slenderStaticFranticInterval(getInt(properties, "slenderStaticFranticInterval", internal.slenderStaticFranticInterval()))
- .slenderStaticMinVolume(getFloat(properties, "slenderStaticMinVolume", internal.slenderStaticMinVolume()))
- .slenderStaticMaxVolume(getFloat(properties, "slenderStaticMaxVolume", internal.slenderStaticMaxVolume()))
- .lobbyAtmosphereShare(getFloat(properties, "lobbyAtmosphereShare", internal.lobbyAtmosphereShare()));
-
- return configBuilder.build();
+ GameConfig.Round round = GameConfig.Round.DEFAULT;
+ GameConfig.Teams teams = GameConfig.Teams.DEFAULT;
+ GameConfig.PageProximity proximity = GameConfig.PageProximity.DEFAULT;
+ GameConfig.DamageSound damage = GameConfig.DamageSound.DEFAULT;
+ GameConfig.Glitch glitch = GameConfig.Glitch.DEFAULT;
+ GameConfig.SlenderStatic slenderStatic = GameConfig.SlenderStatic.DEFAULT;
+ return new GameConfig(
+ new GameConfig.Round(
+ getInt(properties, "minPlayers", round.minPlayers()),
+ getInt(properties, "maxPlayers", round.maxPlayers()),
+ getInt(properties, "lobbyTime", round.lobbyTime()),
+ getInt(properties, "gameTime", round.gameTime())
+ ),
+ new GameConfig.Teams(
+ getInt(properties, "slenderTeamSize", teams.slenderSize()),
+ getInt(properties, "survivorTeamSize", teams.survivorSize())
+ ),
+ getString(properties, SENTRY_DSN_KEY),
+ new GameConfig.ResourcePack(getResourcePackUrl(properties), getResourcePackSha1(properties)),
+ new GameConfig.PageProximity(
+ getBoolean(properties, "pageProximityEnabled", proximity.enabled()),
+ getInt(properties, "pageProximityRange", proximity.range()),
+ getSound(properties, PAGE_PROXIMITY_SOUND_KEY, proximity.sound()),
+ getFloat(properties, "pageProximityVolumeFactor", proximity.volumeFactor())
+ ),
+ new GameConfig.DamageSound(
+ getBoolean(properties, "damageSoundEnabled", damage.enabled()),
+ getInt(properties, "damageSoundCooldown", damage.cooldown()),
+ getSound(properties, DAMAGE_SOUND_KEY, damage.sound())
+ ),
+ new GameConfig.Glitch(
+ getInt(properties, "glitchRange", glitch.range()),
+ getInt(properties, "glitchCloseRange", glitch.closeRange()),
+ getInt(properties, "glitchViewAngle", glitch.viewAngle())
+ ),
+ new GameConfig.SlenderStatic(
+ getBoolean(properties, "slenderStaticEnabled", slenderStatic.enabled()),
+ getSound(properties, SLENDER_STATIC_SOUND_KEY, slenderStatic.sound()),
+ getInt(properties, "slenderStaticQuietInterval", slenderStatic.quietInterval()),
+ getInt(properties, "slenderStaticFranticInterval", slenderStatic.franticInterval()),
+ getFloat(properties, "slenderStaticMinVolume", slenderStatic.minVolume()),
+ getFloat(properties, "slenderStaticMaxVolume", slenderStatic.maxVolume())
+ ),
+ getFloat(properties, "lobbyAtmosphereShare", GameConfig.DEFAULT.lobbyAtmosphereShare())
+ );
}
private int getInt(Properties properties, String key, int defaultValue) {
diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/config/InternalGameConfig.java b/common/src/main/java/net/onelitefeather/cygnus/common/config/InternalGameConfig.java
deleted file mode 100644
index 793ebcb4..00000000
--- a/common/src/main/java/net/onelitefeather/cygnus/common/config/InternalGameConfig.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package net.onelitefeather.cygnus.common.config;
-
-import net.kyori.adventure.key.Key;
-import org.jetbrains.annotations.Nullable;
-
-import java.net.URI;
-
-/**
- * The {@link InternalGameConfig} is the fallback configuration if no other configuration is available.
- * It provides default values for the game configuration.
- * These values should be only modified if the case is necessary and the default values are not suitable.
- *
- * @param minPlayers the minimum number of players required to start a game
- * @param maxPlayers the maximum number of players allowed in the game
- * @param lobbyTime the time in seconds before the game starts
- * @param gameTime the maximum duration of a game in seconds
- * @param slenderTeamSize the size of the slender team
- * @param survivorTeamSize the size of the survivor team
- * @param sentryDsn the DSN to report errors to, or {@code null} to keep Sentry off
- * @param resourcePackUrl the location the client downloads the ResourcePack from, or {@code null}
- * to keep the ResourcePack feature off
- * @param resourcePackSha1 the checksum of the ResourcePack, or {@code null} to have it computed
- * @param pageProximityEnabled whether survivors hear a sound while a page is nearby
- * @param pageProximityRange how far away a page may be and still be heard, in blocks
- * @param pageProximityInterval the number of ticks between two proximity sounds
- * @param pageProximitySound the sound played while a page is nearby
- * @param pageProximityVolumeFactor how far past the range the chime's falloff is stretched
- * @param damageSoundEnabled whether a player hears a sound when they take damage
- * @param damageSoundCooldown the number of ticks before the damage sound is played again
- * @param damageSound the sound played to a player who was just hit
- * @param glitchRange how close the slender has to be before the sight of him tears a
- * survivor's view, in blocks
- * @param glitchCloseRange the distance in blocks at which the tearing is at its worst
- * @param glitchViewAngle how far off the centre of their view he may stand and still count
- * as seen, in degrees
- * @param lobbyAtmosphereShare how far the lobby's atmosphere is taken towards the map's own
- * @param slenderStaticEnabled whether the slender hears static as his pages are collected
- * @param slenderStaticSound the sound the static is built from
- * @param slenderStaticQuietInterval the seconds between two bursts while no page has been found
- * @param slenderStaticFranticInterval the seconds between two bursts once every page is gone
- * @param slenderStaticMinVolume how loud the static is while no page has been found
- * @param slenderStaticMaxVolume how loud the static is once every page is gone
- * @author theEvilReaper
- * @version 1.4.0
- * @since 1.0.0
- */
-record InternalGameConfig(
- int minPlayers,
- int maxPlayers,
- int lobbyTime,
- int gameTime,
- int slenderTeamSize,
- int survivorTeamSize,
- @Nullable String sentryDsn,
- @Nullable URI resourcePackUrl,
- @Nullable String resourcePackSha1,
- boolean pageProximityEnabled,
- int pageProximityRange,
- int pageProximityInterval,
- Key pageProximitySound,
- float pageProximityVolumeFactor,
- boolean damageSoundEnabled,
- int damageSoundCooldown,
- Key damageSound,
- int glitchRange,
- int glitchCloseRange,
- int glitchViewAngle,
- boolean slenderStaticEnabled,
- Key slenderStaticSound,
- int slenderStaticQuietInterval,
- int slenderStaticFranticInterval,
- float slenderStaticMinVolume,
- float slenderStaticMaxVolume,
- float lobbyAtmosphereShare
-) implements GameConfig {
-
- // Sentry and the ResourcePack are opt-in: a service that says nothing about them reports to
- // nothing and pushes nothing, which is what a local run without a config file needs.
- // The proximity hint is on by default: a page that cannot be heard at all is the pre-2.12
- // behaviour, and a map is easier to play with the hint than without it.
- // The damage feedback is on by default for the same reason: taking a hit in silence is a bug,
- // not a setting. The cooldown of 20 ticks lets through every second damage tick of a draining
- // slender, which is enough to notice and not enough to grate.
- // The slender's static is on by default too: it is the only thing that tells him how far the
- // survivors have got without putting the page counter in front of him.
- private static final GameConfig DEFAULT = new InternalGameConfig(
- 2, 13, 30, 900, 1, 12, null, null, null,
- true, 20, 20, GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND,
- GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR,
- true, 20, GameConfig.DEFAULT_DAMAGE_SOUND,
- GameConfig.DEFAULT_GLITCH_RANGE,
- GameConfig.DEFAULT_GLITCH_CLOSE_RANGE,
- GameConfig.DEFAULT_GLITCH_VIEW_ANGLE,
- true, GameConfig.DEFAULT_SLENDER_STATIC_SOUND,
- GameConfig.DEFAULT_SLENDER_STATIC_QUIET_INTERVAL,
- GameConfig.DEFAULT_SLENDER_STATIC_FRANTIC_INTERVAL,
- GameConfig.DEFAULT_SLENDER_STATIC_MIN_VOLUME,
- GameConfig.DEFAULT_SLENDER_STATIC_MAX_VOLUME,
- GameConfig.DEFAULT_LOBBY_ATMOSPHERE_SHARE);
-
- /**
- * Returns the default configuration for the game.
- *
- * @return the default configuration
- */
- public static GameConfig defaultConfig() {
- return DEFAULT;
- }
-}
diff --git a/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigReaderTest.java b/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigReaderTest.java
index 5002ec07..d8880952 100644
--- a/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigReaderTest.java
+++ b/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigReaderTest.java
@@ -22,19 +22,10 @@ void testValidConfigRead() {
fail("Config file not found");
}
- GameConfigReader gameConfigReader = new GameConfigReader(origin);
- assertNotNull(gameConfigReader);
-
- GameConfig gameConfig = gameConfigReader.getConfig();
- assertNotNull(gameConfig);
- assertInstanceOf(GameConfig.class, gameConfig);
-
- assertEquals(4, gameConfig.minPlayers());
- assertEquals(10, gameConfig.maxPlayers());
- assertEquals(30, gameConfig.lobbyTime());
- assertEquals(300, gameConfig.gameTime());
- assertEquals(1, gameConfig.slenderTeamSize());
- assertEquals(12, gameConfig.survivorTeamSize());
+ GameConfig gameConfig = new GameConfigReader(origin).getConfig();
+
+ assertEquals(new GameConfig.Round(4, 10, 30, 300), gameConfig.round());
+ assertEquals(new GameConfig.Teams(1, 12), gameConfig.teams());
}
@Test
@@ -49,20 +40,29 @@ void testInvalidConfigReadFallback(@org.junit.jupiter.api.io.TempDir Path tempDi
assertNotNull(config);
// "minPlayers" was invalid, should fall back to default (2)
- assertEquals(2, config.minPlayers());
+ assertEquals(2, config.round().minPlayers());
// "maxPlayers" was valid, should parse correctly (15)
- assertEquals(15, config.maxPlayers());
+ assertEquals(15, config.round().maxPlayers());
// "lobbyTime" was invalid, should fall back to default (30)
- assertEquals(30, config.lobbyTime());
+ assertEquals(30, config.round().lobbyTime());
}
@Test
- void testOptionalValuesAreAbsentWhenTheyAreNotConfigured() {
+ void testGroupsMissingFromTheFileKeepTheirDefaults() {
GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
assertNull(config.sentryDsn());
- assertNull(config.resourcePackUrl());
- assertNull(config.resourcePackSha1());
+ assertEquals(GameConfig.ResourcePack.NONE, config.resourcePack());
+ assertEquals(GameConfig.PageProximity.DEFAULT, config.pageProximity());
+ assertEquals(GameConfig.DamageSound.DEFAULT, config.damageSound());
+ assertEquals(GameConfig.Glitch.DEFAULT, config.glitch());
+ assertEquals(GameConfig.SlenderStatic.DEFAULT, config.slenderStatic());
+ assertEquals(GameConfig.DEFAULT_LOBBY_ATMOSPHERE_SHARE, config.lobbyAtmosphereShare());
+ }
+
+ @Test
+ void testAMissingFileGivesTheDefaultConfig() {
+ assertSame(GameConfig.DEFAULT, new GameConfigReader(Paths.get("")).getConfig());
}
@Test
@@ -77,8 +77,8 @@ void testOptionalValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) throw
GameConfig config = new GameConfigReader(tempDir).getConfig();
assertEquals("https://key@sentry.example.com/1", config.sentryDsn());
- assertEquals(URI.create("https://example.com/pack.zip"), config.resourcePackUrl());
- assertEquals("a".repeat(40), config.resourcePackSha1());
+ assertEquals(URI.create("https://example.com/pack.zip"), config.resourcePack().url());
+ assertEquals("a".repeat(40), config.resourcePack().sha1());
}
@Test
@@ -93,8 +93,8 @@ void testBlankOptionalValuesCountAsAbsent(@TempDir Path tempDir) throws IOExcept
GameConfig config = new GameConfigReader(tempDir).getConfig();
assertNull(config.sentryDsn());
- assertNull(config.resourcePackUrl());
- assertNull(config.resourcePackSha1());
+ assertNull(config.resourcePack().url());
+ assertNull(config.resourcePack().sha1());
}
@Test
@@ -107,7 +107,7 @@ void testAMalformedResourcePackUrlDisablesTheFeature(@TempDir Path tempDir) thro
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertNull(config.resourcePackUrl());
+ assertNull(config.resourcePack().url());
}
@Test
@@ -120,18 +120,8 @@ void testAMalformedChecksumIsDroppedSoItGetsComputed(@TempDir Path tempDir) thro
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(URI.create("https://example.com/pack.zip"), config.resourcePackUrl());
- assertNull(config.resourcePackSha1());
- }
-
- @Test
- void testPageProximityDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertTrue(config.pageProximityEnabled());
- assertEquals(20, config.pageProximityRange());
- assertEquals(20, config.pageProximityInterval());
- assertEquals(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND, config.pageProximitySound());
+ assertEquals(URI.create("https://example.com/pack.zip"), config.resourcePack().url());
+ assertNull(config.resourcePack().sha1());
}
@Test
@@ -139,16 +129,14 @@ void testPageProximityValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir)
Files.writeString(tempDir.resolve("config.properties"), """
minPlayers=4
pageProximityRange=32
- pageProximityInterval=40
pageProximitySound=block.note_block.chime
""");
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertTrue(config.pageProximityEnabled());
- assertEquals(32, config.pageProximityRange());
- assertEquals(40, config.pageProximityInterval());
- assertEquals(Key.key("block.note_block.chime"), config.pageProximitySound());
+ assertTrue(config.pageProximity().enabled());
+ assertEquals(32, config.pageProximity().range());
+ assertEquals(Key.key("block.note_block.chime"), config.pageProximity().sound());
}
@Test
@@ -160,7 +148,7 @@ void testPageProximityCanBeTurnedOff(@TempDir Path tempDir) throws IOException {
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertFalse(config.pageProximityEnabled());
+ assertFalse(config.pageProximity().enabled());
}
@Test
@@ -172,7 +160,7 @@ void testAMalformedSoundKeyFallsBackToTheDefault(@TempDir Path tempDir) throws I
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND, config.pageProximitySound());
+ assertEquals(GameConfig.PageProximity.DEFAULT_SOUND, config.pageProximity().sound());
}
@Test
@@ -180,25 +168,13 @@ void testARangeBeyondTheMaximumIsRejected(@TempDir Path tempDir) throws IOExcept
Files.writeString(tempDir.resolve("config.properties"), """
minPlayers=4
pageProximityRange=%d
- """.formatted(GameConfig.MAX_PAGE_PROXIMITY_RANGE + 1));
+ """.formatted(GameConfig.PageProximity.MAX_RANGE + 1));
GameConfigReader reader = new GameConfigReader(tempDir);
assertThrows(IllegalArgumentException.class, reader::getConfig);
}
- @Test
- void testSlenderStaticDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertTrue(config.slenderStaticEnabled());
- assertEquals(GameConfig.DEFAULT_SLENDER_STATIC_SOUND, config.slenderStaticSound());
- assertEquals(GameConfig.DEFAULT_SLENDER_STATIC_QUIET_INTERVAL, config.slenderStaticQuietInterval());
- assertEquals(GameConfig.DEFAULT_SLENDER_STATIC_FRANTIC_INTERVAL, config.slenderStaticFranticInterval());
- assertEquals(GameConfig.DEFAULT_SLENDER_STATIC_MIN_VOLUME, config.slenderStaticMinVolume());
- assertEquals(GameConfig.DEFAULT_SLENDER_STATIC_MAX_VOLUME, config.slenderStaticMaxVolume());
- }
-
@Test
void testSlenderStaticValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) throws IOException {
Files.writeString(tempDir.resolve("config.properties"), """
@@ -212,12 +188,12 @@ void testSlenderStaticValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir)
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(Key.key("cygnus", "vhs_static"), config.slenderStaticSound(),
+ assertEquals(Key.key("cygnus", "vhs_static"), config.slenderStatic().sound(),
"a resource pack sound has to survive the reader, it is the point of the setting");
- assertEquals(20, config.slenderStaticQuietInterval());
- assertEquals(2, config.slenderStaticFranticInterval());
- assertEquals(0.1F, config.slenderStaticMinVolume());
- assertEquals(1.0F, config.slenderStaticMaxVolume());
+ assertEquals(20, config.slenderStatic().quietInterval());
+ assertEquals(2, config.slenderStatic().franticInterval());
+ assertEquals(0.1F, config.slenderStatic().minVolume());
+ assertEquals(1.0F, config.slenderStatic().maxVolume());
}
@Test
@@ -229,7 +205,7 @@ void testSlenderStaticCanBeTurnedOff(@TempDir Path tempDir) throws IOException {
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertFalse(config.slenderStaticEnabled());
+ assertFalse(config.slenderStatic().enabled());
}
@Test
@@ -245,13 +221,6 @@ void testAFranticIntervalAtOrAboveTheQuietOneIsRejected(@TempDir Path tempDir) t
assertThrows(IllegalArgumentException.class, reader::getConfig);
}
- @Test
- void testLobbyAtmosphereShareDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertEquals(GameConfig.DEFAULT_LOBBY_ATMOSPHERE_SHARE, config.lobbyAtmosphereShare());
- }
-
@Test
void testLobbyAtmosphereShareIsReadWhenItIsConfigured(@TempDir Path tempDir) throws IOException {
Files.writeString(tempDir.resolve("config.properties"), """
@@ -276,15 +245,6 @@ void testALobbyAtmosphereShareOutsideItsRangeIsRejected(@TempDir Path tempDir) t
assertThrows(IllegalArgumentException.class, reader::getConfig);
}
- @Test
- void testDamageSoundDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertTrue(config.damageSoundEnabled());
- assertEquals(20, config.damageSoundCooldown());
- assertEquals(GameConfig.DEFAULT_DAMAGE_SOUND, config.damageSound());
- }
-
@Test
void testDamageSoundValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) throws IOException {
Files.writeString(tempDir.resolve("config.properties"), """
@@ -295,9 +255,9 @@ void testDamageSoundValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) th
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertTrue(config.damageSoundEnabled());
- assertEquals(30, config.damageSoundCooldown());
- assertEquals(Key.key("entity.player.big_fall"), config.damageSound());
+ assertTrue(config.damageSound().enabled());
+ assertEquals(30, config.damageSound().cooldown());
+ assertEquals(Key.key("entity.player.big_fall"), config.damageSound().sound());
}
@Test
@@ -309,7 +269,7 @@ void testDamageSoundCanBeTurnedOff(@TempDir Path tempDir) throws IOException {
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertFalse(config.damageSoundEnabled());
+ assertFalse(config.damageSound().enabled());
}
@Test
@@ -321,7 +281,7 @@ void testAMalformedDamageSoundKeyFallsBackToTheDefault(@TempDir Path tempDir) th
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(GameConfig.DEFAULT_DAMAGE_SOUND, config.damageSound());
+ assertEquals(GameConfig.DamageSound.DEFAULT_SOUND, config.damageSound().sound());
}
@Test
@@ -336,15 +296,6 @@ void testADamageSoundCooldownBelowOneTickIsRejected(@TempDir Path tempDir) throw
assertThrows(IllegalArgumentException.class, reader::getConfig);
}
- @Test
- void testGlitchDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertEquals(GameConfig.DEFAULT_GLITCH_RANGE, config.glitchRange());
- assertEquals(GameConfig.DEFAULT_GLITCH_CLOSE_RANGE, config.glitchCloseRange());
- assertEquals(GameConfig.DEFAULT_GLITCH_VIEW_ANGLE, config.glitchViewAngle());
- }
-
@Test
void testGlitchValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) throws IOException {
Files.writeString(tempDir.resolve("config.properties"), """
@@ -356,9 +307,9 @@ void testGlitchValuesAreReadWhenTheyAreConfigured(@TempDir Path tempDir) throws
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(20, config.glitchRange());
- assertEquals(6, config.glitchCloseRange());
- assertEquals(45, config.glitchViewAngle());
+ assertEquals(20, config.glitch().range());
+ assertEquals(6, config.glitch().closeRange());
+ assertEquals(45, config.glitch().viewAngle());
}
/**
@@ -376,15 +327,8 @@ void testAnUnreadableGlitchValueFallsBackToTheDefault(@TempDir Path tempDir) thr
GameConfig config = new GameConfigReader(tempDir).getConfig();
- assertEquals(GameConfig.DEFAULT_GLITCH_RANGE, config.glitchRange());
- assertEquals(6, config.glitchCloseRange());
- }
-
- @Test
- void testThePageProximityVolumeFactorDefaultsWhenNothingIsConfigured() {
- GameConfig config = new GameConfigReader(Paths.get("src", "test", "resources")).getConfig();
-
- assertEquals(GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR, config.pageProximityVolumeFactor());
+ assertEquals(GameConfig.Glitch.DEFAULT.range(), config.glitch().range());
+ assertEquals(6, config.glitch().closeRange());
}
@Test
@@ -394,7 +338,7 @@ void testThePageProximityVolumeFactorIsReadWhenItIsConfigured(@TempDir Path temp
pageProximityVolumeFactor=3.5
""");
- assertEquals(3.5F, new GameConfigReader(tempDir).getConfig().pageProximityVolumeFactor());
+ assertEquals(3.5F, new GameConfigReader(tempDir).getConfig().pageProximity().volumeFactor());
}
/**
@@ -408,7 +352,7 @@ void testAnUnreadableVolumeFactorFallsBackToTheDefault(@TempDir Path tempDir) th
pageProximityVolumeFactor=loud
""");
- assertEquals(GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR,
- new GameConfigReader(tempDir).getConfig().pageProximityVolumeFactor());
+ assertEquals(GameConfig.PageProximity.DEFAULT_VOLUME_FACTOR,
+ new GameConfigReader(tempDir).getConfig().pageProximity().volumeFactor());
}
}
diff --git a/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigTest.java b/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigTest.java
index b9a80981..b44f1c8f 100644
--- a/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigTest.java
+++ b/common/src/test/java/net/onelitefeather/cygnus/common/config/GameConfigTest.java
@@ -1,160 +1,82 @@
package net.onelitefeather.cygnus.common.config;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
class GameConfigTest {
@Test
- void testInvalidSlenderUsage() {
- GameConfig.Builder builder = GameConfig.builder();
- assertNotNull(builder);
+ void testDefaultConfig() {
+ GameConfig config = GameConfig.DEFAULT;
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> builder.slenderTeamSize(0));
- assertNotNull(exception);
-
- assertEquals("Slender team size must be at least 1", exception.getMessage());
+ assertEquals(new GameConfig.Round(2, 13, 30, 900), config.round());
+ assertEquals(new GameConfig.Teams(1, 12), config.teams());
+ assertNull(config.sentryDsn());
+ assertEquals(GameConfig.ResourcePack.NONE, config.resourcePack());
}
@Test
- void testInvalidSurvivorUsage() {
- GameConfig.Builder builder = GameConfig.builder();
- assertNotNull(builder);
-
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> builder.survivorTeamSize(0));
- assertNotNull(exception);
- assertEquals("Survivor team size must be at least 2", exception.getMessage());
+ void testInvalidTeamSizes() {
+ assertRejected("Slender team size must be at least 1", () -> new GameConfig.Teams(0, 12));
+ assertRejected("Survivor team size must be at least 2", () -> new GameConfig.Teams(1, 0));
}
@Test
- void testInvalidLobbyTimeUsage() {
- GameConfig.Builder builder = GameConfig.builder();
- assertNotNull(builder);
-
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> builder.lobbyTime(0));
- assertNotNull(exception);
- assertEquals("Lobby time must be greater than " + GameConfig.FORCE_START_TIME, exception.getMessage());
+ void testInvalidLobbyTime() {
+ assertRejected("Lobby time must be greater than " + GameConfig.FORCE_START_TIME,
+ () -> new GameConfig.Round(2, 10, GameConfig.FORCE_START_TIME, 600));
}
@Test
- void testInvalidDamageSoundCooldownUsage() {
- GameConfig.Builder builder = GameConfig.builder();
- assertNotNull(builder);
-
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> builder.damageSoundCooldown(0));
- assertNotNull(exception);
- assertEquals("Damage sound cooldown must be at least 1 tick", exception.getMessage());
+ void testInvalidDamageSoundCooldown() {
+ assertRejected("Damage sound cooldown must be at least 1 tick",
+ () -> new GameConfig.DamageSound(true, 0, GameConfig.DamageSound.DEFAULT_SOUND));
}
@Test
- void testBuilderUsage() {
- GameConfig.Builder builder = GameConfig.builder();
- assertNotNull(builder);
-
- builder.gameTime(500).lobbyTime(12).minPlayers(1).maxPlayers(12).survivorTeamSize(2).slenderTeamSize(2);
-
- GameConfig config = builder.build();
-
- assertEquals(500, config.gameTime());
- assertEquals(12, config.lobbyTime());
- assertEquals(1, config.minPlayers());
- assertEquals(12, config.maxPlayers());
- assertEquals(2, config.survivorTeamSize());
+ void testInvalidLobbyAtmosphereShare() {
+ GameConfig defaults = GameConfig.DEFAULT;
+ assertRejected("Lobby atmosphere share must be between 0 and 1", () -> new GameConfig(
+ defaults.round(), defaults.teams(), null, defaults.resourcePack(), defaults.pageProximity(),
+ defaults.damageSound(), defaults.glitch(), defaults.slenderStatic(), 1.5F));
}
-
@Test
void testGlitchRangeRejectsValuesOutsideTheAllowedRange() {
- GameConfig.Builder builder = GameConfig.builder();
-
- String expected = "Glitch range must be between 1 and " + GameConfig.MAX_GLITCH_RANGE;
- assertEquals(expected, assertThrows(IllegalArgumentException.class, () -> builder.glitchRange(0)).getMessage());
- assertEquals(expected, assertThrows(IllegalArgumentException.class,
- () -> builder.glitchRange(GameConfig.MAX_GLITCH_RANGE + 1)).getMessage());
+ String expected = "Glitch range must be between 1 and " + GameConfig.Glitch.MAX_RANGE;
+ assertRejected(expected, () -> new GameConfig.Glitch(0, 4, 30));
+ assertRejected(expected, () -> new GameConfig.Glitch(GameConfig.Glitch.MAX_RANGE + 1, 4, 30));
}
@Test
void testGlitchCloseRangeRejectsValuesBelowOne() {
- GameConfig.Builder builder = GameConfig.builder();
-
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> builder.glitchCloseRange(0));
- assertEquals("Glitch close range must be at least 1 block", exception.getMessage());
+ assertRejected("Glitch close range must be at least 1 block", () -> new GameConfig.Glitch(12, 0, 30));
}
@Test
void testGlitchViewAngleRejectsValuesOutsideTheAllowedRange() {
- GameConfig.Builder builder = GameConfig.builder();
-
- String expected = "Glitch view angle must be between 1 and " + GameConfig.MAX_GLITCH_VIEW_ANGLE + " degrees";
- assertEquals(expected, assertThrows(IllegalArgumentException.class, () -> builder.glitchViewAngle(0)).getMessage());
- assertEquals(expected, assertThrows(IllegalArgumentException.class,
- () -> builder.glitchViewAngle(GameConfig.MAX_GLITCH_VIEW_ANGLE + 1)).getMessage());
+ String expected = "Glitch view angle must be between 1 and " + GameConfig.Glitch.MAX_VIEW_ANGLE + " degrees";
+ assertRejected(expected, () -> new GameConfig.Glitch(12, 4, 0));
+ assertRejected(expected, () -> new GameConfig.Glitch(12, 4, GameConfig.Glitch.MAX_VIEW_ANGLE + 1));
}
/**
- * Each of the two distances is a valid number on its own, so neither setter can reject this
- * pairing - only {@code build()} sees both. Without the check the slope between them would
- * divide by zero or run backwards.
+ * Each of the two distances is a valid number on its own. Without the check the slope between
+ * them would divide by zero or run backwards.
*/
@Test
void testGlitchCloseRangeMustStayBelowTheGlitchRange() {
- GameConfig.Builder equal = GameConfig.builder().glitchRange(12).glitchCloseRange(12);
- assertEquals("Glitch close range (12) must be below the glitch range (12)",
- assertThrows(IllegalArgumentException.class, equal::build).getMessage());
-
- GameConfig.Builder crossed = GameConfig.builder().glitchRange(8).glitchCloseRange(16);
- assertEquals("Glitch close range (16) must be below the glitch range (8)",
- assertThrows(IllegalArgumentException.class, crossed::build).getMessage());
- }
-
- @Test
- void testGlitchValuesReachTheBuiltConfiguration() {
- GameConfig config = GameConfig.builder()
- .lobbyTime(12)
- .glitchRange(20)
- .glitchCloseRange(6)
- .glitchViewAngle(45)
- .build();
-
- assertEquals(20, config.glitchRange());
- assertEquals(6, config.glitchCloseRange());
- assertEquals(45, config.glitchViewAngle());
- }
-
- /**
- * A builder that says nothing about the gaze still has to produce a usable configuration -
- * otherwise every caller would be forced to set three values it has no opinion on.
- */
- @Test
- void testGlitchDefaultsApplyWhenTheBuilderSaysNothing() {
- GameConfig config = GameConfig.builder().lobbyTime(12).build();
-
- assertEquals(GameConfig.DEFAULT_GLITCH_RANGE, config.glitchRange());
- assertEquals(GameConfig.DEFAULT_GLITCH_CLOSE_RANGE, config.glitchCloseRange());
- assertEquals(GameConfig.DEFAULT_GLITCH_VIEW_ANGLE, config.glitchViewAngle());
+ assertRejected("Glitch close range (12) must be below the glitch range (12)", () -> new GameConfig.Glitch(12, 12, 30));
+ assertRejected("Glitch close range (16) must be below the glitch range (8)", () -> new GameConfig.Glitch(8, 16, 30));
}
@Test
void testPageProximityVolumeFactorRejectsValuesOutsideTheAllowedRange() {
- GameConfig.Builder builder = GameConfig.builder();
-
- String expected = "Page proximity volume factor must be between 1 and "
- + GameConfig.MAX_PAGE_PROXIMITY_VOLUME_FACTOR;
- assertEquals(expected, assertThrows(IllegalArgumentException.class,
- () -> builder.pageProximityVolumeFactor(0.5F)).getMessage());
- assertEquals(expected, assertThrows(IllegalArgumentException.class,
- () -> builder.pageProximityVolumeFactor(GameConfig.MAX_PAGE_PROXIMITY_VOLUME_FACTOR + 1)).getMessage());
- }
-
- @Test
- void testPageProximityVolumeFactorReachesTheBuiltConfiguration() {
- GameConfig config = GameConfig.builder().lobbyTime(12).pageProximityVolumeFactor(3.5F).build();
-
- assertEquals(3.5F, config.pageProximityVolumeFactor());
+ String expected = "Page proximity volume factor must be between 1 and " + GameConfig.PageProximity.MAX_VOLUME_FACTOR;
+ assertRejected(expected, () -> proximityWithFactor(0.5F));
+ assertRejected(expected, () -> proximityWithFactor(GameConfig.PageProximity.MAX_VOLUME_FACTOR + 1));
}
/**
@@ -163,10 +85,15 @@ void testPageProximityVolumeFactorReachesTheBuiltConfiguration() {
*/
@Test
void testTheDefaultVolumeFactorStretchesPastTheRange() {
- GameConfig config = GameConfig.builder().lobbyTime(12).build();
-
- assertEquals(GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR, config.pageProximityVolumeFactor());
- assertTrue(GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR > 1.0F,
+ assertTrue(GameConfig.PageProximity.DEFAULT.volumeFactor() > 1.0F,
"a factor of 1 puts the chime's silence exactly at the range's edge");
}
+
+ private static GameConfig.PageProximity proximityWithFactor(float volumeFactor) {
+ return new GameConfig.PageProximity(true, 20, GameConfig.PageProximity.DEFAULT_SOUND, volumeFactor);
+ }
+
+ private static void assertRejected(String message, Executable creation) {
+ assertEquals(message, assertThrows(IllegalArgumentException.class, creation).getMessage());
+ }
}
diff --git a/common/src/test/java/net/onelitefeather/cygnus/common/config/InternalGameConfigTest.java b/common/src/test/java/net/onelitefeather/cygnus/common/config/InternalGameConfigTest.java
deleted file mode 100644
index 464f9086..00000000
--- a/common/src/test/java/net/onelitefeather/cygnus/common/config/InternalGameConfigTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.onelitefeather.cygnus.common.config;
-
-import org.junit.jupiter.api.Test;
-
-import java.nio.file.Paths;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InternalGameConfigTest {
-
- @Test
- void testInternalConfigUsage() {
- GameConfigReader gameConfigReader = new GameConfigReader(Paths.get(""));
- assertNotNull(gameConfigReader);
-
- GameConfig gameConfig = gameConfigReader.getConfig();
- assertNotNull(gameConfig);
- assertInstanceOf(InternalGameConfig.class, gameConfig);
-
- assertEquals(2, gameConfig.minPlayers());
- assertEquals(13, gameConfig.maxPlayers());
- assertEquals(30, gameConfig.lobbyTime());
- assertEquals(900, gameConfig.gameTime());
- assertEquals(1, gameConfig.slenderTeamSize());
- assertEquals(12, gameConfig.survivorTeamSize());
- }
-}
diff --git a/config.properties.example b/config.properties.example
index 50d25ee4..7ff986a8 100644
--- a/config.properties.example
+++ b/config.properties.example
@@ -73,9 +73,6 @@ survivorTeamSize=12
# range itself either way.
#pageProximityRange=20
-# Ticks between two chimes. At least 1. 20 ticks is one second.
-#pageProximityInterval=20
-
# The sound played. A key that names no sound the server knows falls back to the
# default, so the hint never goes silent because of a typo.
#pageProximitySound=block.amethyst_block.chime
diff --git a/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java b/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java
index f426d869..eaddf023 100644
--- a/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java
+++ b/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java
@@ -144,7 +144,7 @@ public Cygnus() {
// Set up as early as possible so anything that goes wrong while the rest of the game is
// being wired up is already covered. Stays off entirely when no DSN is configured.
SentrySupport.init(this.gameConfig.sentryDsn());
- this.resourcePackService = ResourcePackService.create(this.gameConfig);
+ this.resourcePackService = ResourcePackService.create(this.gameConfig.resourcePack());
// Every player needs the pack id so it can hand the pack back when it is kicked; see
// CygnusPlayer#kick. Null when the ResourcePack feature is off, which leaves the kick untouched.
UUID resourcePackId = this.resourcePackService.map(ResourcePackService::packId).orElse(null);
@@ -155,7 +155,7 @@ public Cygnus() {
// Falco keeps its region files open, so the loaders have to be released on shutdown
MinecraftServer.getSchedulerManager().buildShutdownTask(this.mapProvider::close);
this.view = new GameViewImpl();
- this.createTeams(this.gameConfig, this.teamService);
+ this.createTeams(this.gameConfig.teams(), this.teamService);
this.scoreboardDisplay = new ScoreboardDisplay(this.teamService.getTeams());
Team survivorTeam = this.teamService.getTeam(GameConfig.SURVIVOR_KEY)
.orElseThrow(() -> new IllegalStateException("Survivor team not found"));
@@ -163,7 +163,7 @@ public Cygnus() {
// Only survivors get the hint: the slender hearing it would turn every page into a place to
// camp at, which is the opposite of what the hint is for.
this.pageProximityService = new PageProximityService(
- this.gameConfig,
+ this.gameConfig.pageProximity(),
survivorTeam::getPlayers,
this.pageProvider::interactablePages
);
@@ -178,17 +178,17 @@ public Cygnus() {
this.slenderGazeService = new SlenderGazeService(
this.gazeSignal,
new SlenderGaze(
- this.gameConfig.glitchRange(),
- this.gameConfig.glitchCloseRange(),
- this.gameConfig.glitchViewAngle()),
+ this.gameConfig.glitch().range(),
+ this.gameConfig.glitch().closeRange(),
+ this.gameConfig.glitch().viewAngle()),
() -> TeamHelper.slenderOf(this.teamService));
this.bloodSplatterService = new BloodSplatterService(
this.screenOverlay,
bound -> ThreadLocalRandom.current().nextInt(bound)
);
- this.damageSoundService = new DamageSoundService(this.gameConfig, System::currentTimeMillis);
+ this.damageSoundService = new DamageSoundService(this.gameConfig.damageSound(), System::currentTimeMillis);
this.slenderStaticService = new SlenderStaticService(
- this.gameConfig,
+ this.gameConfig.slenderStatic(),
() -> TeamHelper.slenderOf(this.teamService));
this.tunnelVisionRenderer = new OverlayTunnelVisionRenderer(this.screenOverlay);
this.tunnelVisionService = new TunnelVisionService(this.tunnelVisionRenderer, player -> StaminaHelper.remainingShare(this.staminaService, player));
@@ -214,12 +214,12 @@ private void initListener() {
);
manager.addListener(GameMapLoadedEvent.class, new GameMapLoadedListener());
manager.addListener(PlayerSpawnEvent.class, new PlayerSpawnListener(player -> this.mapProvider.teleportToSpawn(player, false), phaseSupplier));
- PlayerQuitListener quitListener = new PlayerQuitListener(phaseSupplier, teamService, this.staminaService, this.spectatorService::updateInventory, this.gameConfig.minPlayers());
+ PlayerQuitListener quitListener = new PlayerQuitListener(phaseSupplier, teamService, this.staminaService, this.spectatorService::updateInventory, this.gameConfig.round().minPlayers());
manager.addListener(PlayerDisconnectEvent.class, quitListener);
manager.addListener(AsyncPlayerConfigurationEvent.class,
new PlayerLoginListener(
this.mapProvider.getActiveInstance(),
- this.gameConfig.maxPlayers(),
+ this.gameConfig.round().maxPlayers(),
linearPhaseSeries::getCurrentPhase,
this.resourcePackService
)
@@ -306,10 +306,10 @@ private void initPhases() {
);
MinecraftServer.getSchedulerManager().scheduleNextTick(this.mapProvider::releasePreviousInstance);
};
- LobbyPhase lobbyPhase = new LobbyPhase(this.gameConfig, this.mapProvider.getActiveInstance());
+ LobbyPhase lobbyPhase = new LobbyPhase(this.gameConfig.round(), this.mapProvider.getActiveInstance());
this.linearPhaseSeries.add(lobbyPhase);
this.linearPhaseSeries.add(new WaitingPhase(this.view, instanceSwitch, teamInitializer));
- this.linearPhaseSeries.add(new GamePhase(this.view, this::finishGame, this.gameConfig.gameTime(), this.jumpscareManager));
+ this.linearPhaseSeries.add(new GamePhase(this.view, this::finishGame, this.gameConfig.round().gameTime(), this.jumpscareManager));
this.linearPhaseSeries.add(new RestartPhase());
}
diff --git a/game/src/main/java/net/onelitefeather/cygnus/damage/DamageSoundService.java b/game/src/main/java/net/onelitefeather/cygnus/damage/DamageSoundService.java
index c20598e0..fa0732b8 100644
--- a/game/src/main/java/net/onelitefeather/cygnus/damage/DamageSoundService.java
+++ b/game/src/main/java/net/onelitefeather/cygnus/damage/DamageSoundService.java
@@ -59,7 +59,7 @@ public final class DamageSoundService {
/** @see #VOLUME */
private static final float PITCH = 1.0F;
- private final GameConfig config;
+ private final GameConfig.DamageSound config;
private final LongSupplier clock;
private final Sound sound;
private final long cooldownMillis;
@@ -73,12 +73,12 @@ public final class DamageSoundService {
* @param config the configuration holding the sound and the cooldown
* @param clock supplies the current time in milliseconds
*/
- public DamageSoundService(GameConfig config, LongSupplier clock) {
+ public DamageSoundService(GameConfig.DamageSound config, LongSupplier clock) {
this.config = config;
this.clock = clock;
- this.cooldownMillis = config.damageSoundCooldown() * MILLIS_PER_TICK;
+ this.cooldownMillis = config.cooldown() * MILLIS_PER_TICK;
this.sound = Sound.sound(
- resolveSound(config.damageSound()),
+ resolveSound(config.sound()),
Sound.Source.PLAYER,
VOLUME,
PITCH
@@ -102,7 +102,7 @@ public void registerListener(EventNode