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 @@ *
  • resourcePackSha1
  • *
  • pageProximityEnabled
  • *
  • pageProximityRange
  • - *
  • pageProximityInterval
  • *
  • pageProximitySound
  • *
  • pageProximityVolumeFactor
  • *
  • damageSoundEnabled
  • @@ -50,7 +49,7 @@ * *

    * 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 node) { * @param player the player who was hit */ void play(Player player) { - if (!this.config.damageSoundEnabled()) { + if (!this.config.enabled()) { return; } @@ -131,7 +131,7 @@ public void clear(Player player) { /** * Resolves the configured key against the sound registry. A key that names no known sound would * leave a hit silent again, which is the bug this service exists for, so it falls back to - * {@link GameConfig#DEFAULT_DAMAGE_SOUND}. + * {@link GameConfig.DamageSound#DEFAULT_SOUND}. * * @param key the configured sound key * @return the resolved sound @@ -141,7 +141,7 @@ private static SoundEvent resolveSound(Key key) { if (soundEvent != null) { return soundEvent; } - LOGGER.warn("'{}' names no known sound, falling back to {}", key, GameConfig.DEFAULT_DAMAGE_SOUND); - return SoundEvent.fromKey(GameConfig.DEFAULT_DAMAGE_SOUND); + LOGGER.warn("'{}' names no known sound, falling back to {}", key, GameConfig.DamageSound.DEFAULT_SOUND); + return SoundEvent.fromKey(GameConfig.DamageSound.DEFAULT_SOUND); } } diff --git a/game/src/main/java/net/onelitefeather/cygnus/noise/SlenderStaticService.java b/game/src/main/java/net/onelitefeather/cygnus/noise/SlenderStaticService.java index ec07ede6..547e4909 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/noise/SlenderStaticService.java +++ b/game/src/main/java/net/onelitefeather/cygnus/noise/SlenderStaticService.java @@ -54,7 +54,7 @@ public final class SlenderStaticService { /** The pitch it has dropped to once every page is gone: a tape that has been running too long. */ private static final float WORN_PITCH = 0.7F; - private final GameConfig config; + private final GameConfig.SlenderStatic config; private final Supplier<@Nullable Player> slender; private final SoundEvent sound; private final RepeatingTask task = new RepeatingTask(this::tick); @@ -71,10 +71,10 @@ public final class SlenderStaticService { * @param config the configuration holding the sound, the intervals and the volumes * @param slender supplies the current slender, or {@code null} while there is none */ - public SlenderStaticService(GameConfig config, Supplier<@Nullable Player> slender) { + public SlenderStaticService(GameConfig.SlenderStatic config, Supplier<@Nullable Player> slender) { this.config = config; this.slender = slender; - this.sound = SoundEvent.of(config.slenderStaticSound(), null); + this.sound = SoundEvent.of(config.sound(), null); this.secondsUntilBurst = this.interval(); } @@ -93,7 +93,7 @@ public void registerListener(EventNode node) { * Starts the carpet from the top. Does nothing while the static is turned off. */ public void start() { - if (!this.config.slenderStaticEnabled()) return; + if (!this.config.enabled()) return; this.reset(); this.task.start(TICK_SECONDS, ChronoUnit.SECONDS); } @@ -119,7 +119,7 @@ public boolean isRunning() { * @param event the find */ void onPageFound(PageFoundEvent event) { - if (!this.config.slenderStaticEnabled()) return; + if (!this.config.enabled()) return; this.progress = shareOf(event.foundCount(), event.maxPages()); // The burst restarts the gap so it does not land on top of the carpet's next beat, which // would read as one long noise rather than as two separate things happening. @@ -141,7 +141,7 @@ void tick() { * play it to. */ private void play() { - if (!this.config.slenderStaticEnabled()) return; + if (!this.config.enabled()) return; Player currentSlender = this.slender.get(); if (currentSlender == null) return; currentSlender.playSound( @@ -163,8 +163,8 @@ private void reset() { */ private int interval() { return Math.round(lerp( - this.config.slenderStaticQuietInterval(), - this.config.slenderStaticFranticInterval(), + this.config.quietInterval(), + this.config.franticInterval(), this.progress)); } @@ -172,7 +172,7 @@ private int interval() { * @return the volume of a burst at the current progress */ private float volume() { - return lerp(this.config.slenderStaticMinVolume(), this.config.slenderStaticMaxVolume(), this.progress); + return lerp(this.config.minVolume(), this.config.maxVolume(), this.progress); } /** diff --git a/game/src/main/java/net/onelitefeather/cygnus/page/PageProximityService.java b/game/src/main/java/net/onelitefeather/cygnus/page/PageProximityService.java index 40dcac2f..2af9c028 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/page/PageProximityService.java +++ b/game/src/main/java/net/onelitefeather/cygnus/page/PageProximityService.java @@ -58,7 +58,7 @@ public final class PageProximityService { public static final int SERVICE_TICK_RATE = 5; private final RepeatingTask task = new RepeatingTask(this::tick); - private final GameConfig config; + private final GameConfig.PageProximity config; private final Supplier> listeners; private final Supplier> pageSupplier; private final Sound warningSound; @@ -75,17 +75,17 @@ public final class PageProximityService { * @param pageSupplier supplies the proximity page targets that can currently be collected */ public PageProximityService( - GameConfig config, + GameConfig.PageProximity config, Supplier> listeners, Supplier> pageSupplier ) { this.config = config; this.listeners = listeners; this.pageSupplier = pageSupplier; - this.rangeSquared = (double) config.pageProximityRange() * config.pageProximityRange(); + this.rangeSquared = (double) config.range() * config.range(); - SoundEvent soundEvent = resolveSound(config.pageProximitySound()); - float baseVolume = volumeFor(config.pageProximityRange(), config.pageProximityVolumeFactor()); + SoundEvent soundEvent = resolveSound(config.sound()); + float baseVolume = volumeFor(config.range(), config.volumeFactor()); this.warningSound = Sound.sound(soundEvent, Sound.Source.MASTER, baseVolume, WARNING_PITCH); this.criticalSound = Sound.sound(soundEvent, Sound.Source.MASTER, baseVolume * CRITICAL_VOLUME_MULTIPLIER, CRITICAL_PITCH); @@ -96,7 +96,7 @@ public PageProximityService( * running. */ public void startTask() { - if (!this.config.pageProximityEnabled()) { + if (!this.config.enabled()) { LOGGER.debug("The page proximity hint is turned off, no task is scheduled"); return; } @@ -121,7 +121,7 @@ public boolean isRunning() { * Plays the chime from every collectible page that is within range of a listener and eligible to chime. */ void tick() { - if (!this.config.pageProximityEnabled()) { + if (!this.config.enabled()) { return; } @@ -191,7 +191,7 @@ private static float volumeFor(int range, float factor) { /** * Resolves the configured key against the sound registry. A key that names no known sound would * leave the hint silent, which is worse than ignoring the configuration, so it falls back to - * {@link GameConfig#DEFAULT_PAGE_PROXIMITY_SOUND}. + * {@link GameConfig.PageProximity#DEFAULT_SOUND}. * * @param key the configured sound key * @return the resolved sound @@ -201,7 +201,7 @@ private static SoundEvent resolveSound(Key key) { if (soundEvent != null) { return soundEvent; } - LOGGER.warn("'{}' names no known sound, falling back to {}", key, GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND); - return SoundEvent.fromKey(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND); + LOGGER.warn("'{}' names no known sound, falling back to {}", key, GameConfig.PageProximity.DEFAULT_SOUND); + return SoundEvent.fromKey(GameConfig.PageProximity.DEFAULT_SOUND); } } diff --git a/game/src/main/java/net/onelitefeather/cygnus/phase/LobbyPhase.java b/game/src/main/java/net/onelitefeather/cygnus/phase/LobbyPhase.java index 28c393fe..2d5f8249 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/phase/LobbyPhase.java +++ b/game/src/main/java/net/onelitefeather/cygnus/phase/LobbyPhase.java @@ -47,22 +47,22 @@ public final class LobbyPhase extends TimedPhase { /** * Constructs a new LobbyPhase. * - * @param gameConfig the configuration settings for the game + * @param round the configured player limits and timings */ - public LobbyPhase(GameConfig gameConfig) { - this(gameConfig, () -> null); + public LobbyPhase(GameConfig.Round round) { + this(round, () -> null); } /** * Constructs a new LobbyPhase with an instance supplier for time transitions. * - * @param gameConfig the configuration settings for the game + * @param round the configured player limits and timings * @param instanceSupplier supplier for the active instance */ - public LobbyPhase(GameConfig gameConfig, Supplier instanceSupplier) { + public LobbyPhase(GameConfig.Round round, Supplier instanceSupplier) { super("Lobby", ChronoUnit.SECONDS, 1); - this.lobbyTime = gameConfig.lobbyTime(); - this.minPlayers = gameConfig.minPlayers(); + this.lobbyTime = round.lobbyTime(); + this.minPlayers = round.minPlayers(); this.setPaused(true); this.setCurrentTicks(lobbyTime); this.setTickDirection(TickDirection.DOWN); diff --git a/game/src/main/java/net/onelitefeather/cygnus/resourcepack/ResourcePackService.java b/game/src/main/java/net/onelitefeather/cygnus/resourcepack/ResourcePackService.java index f19aa994..22b2d9b9 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/resourcepack/ResourcePackService.java +++ b/game/src/main/java/net/onelitefeather/cygnus/resourcepack/ResourcePackService.java @@ -27,7 +27,7 @@ /** * Sends a mandatory ResourcePack to players and kicks them if the client declines it or reports - * a failure. Inactive unless {@link GameConfig#resourcePackUrl()} is configured. + * a failure. Inactive unless {@link GameConfig.ResourcePack#url()} is configured. * * @author theEvilReaper * @version 1.2.0 @@ -77,12 +77,12 @@ private ResourcePackService(URI url, @Nullable String hash) { * @param config the configuration holding the ResourcePack URL and, optionally, its checksum * @return the service, or empty if the feature is disabled */ - public static Optional create(GameConfig config) { - URI url = config.resourcePackUrl(); + public static Optional create(GameConfig.ResourcePack config) { + URI url = config.url(); if (url == null) { return Optional.empty(); } - return Optional.of(new ResourcePackService(url, config.resourcePackSha1())); + return Optional.of(new ResourcePackService(url, config.sha1())); } /** diff --git a/game/src/main/java/net/onelitefeather/cygnus/team/TeamCreator.java b/game/src/main/java/net/onelitefeather/cygnus/team/TeamCreator.java index f562bef0..7f15734d 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/team/TeamCreator.java +++ b/game/src/main/java/net/onelitefeather/cygnus/team/TeamCreator.java @@ -23,22 +23,22 @@ public interface TeamCreator { /** * Creates the teams for the game. * - * @param gameConfig the configuration to get some values from it + * @param teams the configured team sizes * @param teamService the service to add the teams */ - default void createTeams(GameConfig gameConfig, TeamService teamService) { - Team slenderTeam = Team.of(SLENDER_KEY, gameConfig.slenderTeamSize()); + default void createTeams(GameConfig.Teams teams, TeamService teamService) { + Team slenderTeam = Team.of(SLENDER_KEY, teams.slenderSize()); slenderTeam.add(ColorComponent.class, new ColorComponent(ColorData.BLACK)); slenderTeam.add(TeamNameComponent.class, new TeamNameComponent(GameConfig.SLENDER_TEAM_NAME)); teamService.add(slenderTeam); - Team survivorTeam = Team.of(SURVIVOR_KEY, gameConfig.survivorTeamSize()); + Team survivorTeam = Team.of(SURVIVOR_KEY, teams.survivorSize()); survivorTeam.add(ColorComponent.class, new ColorComponent(ColorData.GREEN)); survivorTeam.add(TeamNameComponent.class, new TeamNameComponent(GameConfig.SURVIVOR_TEAM_NAME)); teamService.add(survivorTeam); - Team spectatorTeam = Team.of(SPECTATOR_KEY, gameConfig.survivorTeamSize() + 1); + Team spectatorTeam = Team.of(SPECTATOR_KEY, teams.survivorSize() + 1); spectatorTeam.add(ColorComponent.class, new ColorComponent(ColorData.GRAY)); teamService.add(spectatorTeam); } diff --git a/game/src/test/java/net/onelitefeather/cygnus/TeamCreatorTest.java b/game/src/test/java/net/onelitefeather/cygnus/TeamCreatorTest.java index 10246b08..13d3d8f8 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/TeamCreatorTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/TeamCreatorTest.java @@ -18,7 +18,7 @@ void testTeamCreation() { TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() {}; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); for (int i = 0; i < teamService.getTeams().size(); i++) { assertNotNull(teamService.getTeams().get(i)); diff --git a/game/src/test/java/net/onelitefeather/cygnus/damage/DamageSoundServiceTest.java b/game/src/test/java/net/onelitefeather/cygnus/damage/DamageSoundServiceTest.java index a8f04a1b..a4719fdc 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/damage/DamageSoundServiceTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/damage/DamageSoundServiceTest.java @@ -69,7 +69,7 @@ void nobodyElseHearsTheSound(@NotNull Env env) { bystanderConnection.connect(instance); Collector bystanderSounds = bystanderConnection.trackIncoming(EntitySoundEffectPacket.class); - DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), new AtomicLong()); + DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), new AtomicLong()); service.registerListener(env.process().eventHandler()); EventDispatcher.call(new PlayerDamagedEvent(victim, SOURCE, 1.0F)); @@ -86,7 +86,7 @@ void aSecondHitInsideTheCooldownStaysSilent(@NotNull Env env) { TestConnection connection = env.createConnection(); Player player = connection.connect(instance); AtomicLong clock = new AtomicLong(); - DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), clock); + DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), clock); service.play(player); Collector sounds = connection.trackIncoming(EntitySoundEffectPacket.class); @@ -105,7 +105,7 @@ void theSoundComesBackAfterTheCooldown(@NotNull Env env) { TestConnection connection = env.createConnection(); Player player = connection.connect(instance); AtomicLong clock = new AtomicLong(); - DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), clock); + DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), clock); service.play(player); Collector sounds = connection.trackIncoming(EntitySoundEffectPacket.class); @@ -125,7 +125,7 @@ void theCooldownIsKeptPerPlayer(@NotNull Env env) { Player first = firstConnection.connect(instance); TestConnection secondConnection = env.createConnection(); Player second = secondConnection.connect(instance); - DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), new AtomicLong()); + DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), new AtomicLong()); service.play(first); Collector secondSounds = secondConnection.trackIncoming(EntitySoundEffectPacket.class); @@ -143,7 +143,7 @@ void aTurnedOffFeatureStaysSilent(@NotNull Env env) { TestConnection connection = env.createConnection(); Player player = connection.connect(instance); Collector sounds = connection.trackIncoming(EntitySoundEffectPacket.class); - DamageSoundService service = service(config(false, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), new AtomicLong()); + DamageSoundService service = service(config(false, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), new AtomicLong()); service.registerListener(env.process().eventHandler()); EventDispatcher.call(new PlayerDamagedEvent(player, SOURCE, 1.0F)); @@ -176,7 +176,7 @@ void clearingAPlayerDropsTheCooldown(@NotNull Env env) { Instance instance = env.createFlatInstance(); TestConnection connection = env.createConnection(); Player player = connection.connect(instance); - DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DEFAULT_DAMAGE_SOUND), new AtomicLong()); + DamageSoundService service = service(config(true, COOLDOWN_TICKS, GameConfig.DamageSound.DEFAULT_SOUND), new AtomicLong()); service.play(player); Collector sounds = connection.trackIncoming(EntitySoundEffectPacket.class); @@ -196,7 +196,7 @@ void clearingAPlayerDropsTheCooldown(@NotNull Env env) { * @param clock the milliseconds the service reads the current time from * @return the service under test */ - private static DamageSoundService service(GameConfig config, AtomicLong clock) { + private static DamageSoundService service(GameConfig.DamageSound config, AtomicLong clock) { return new DamageSoundService(config, clock::get); } @@ -208,11 +208,7 @@ private static DamageSoundService service(GameConfig config, AtomicLong clock) { * @param sound the sound key to play * @return the configuration */ - private static GameConfig config(boolean enabled, int cooldown, Key sound) { - return GameConfig.builder() - .damageSoundEnabled(enabled) - .damageSoundCooldown(cooldown) - .damageSound(sound) - .build(); + private static GameConfig.DamageSound config(boolean enabled, int cooldown, Key sound) { + return new GameConfig.DamageSound(enabled, cooldown, sound); } } diff --git a/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerLoginListenerTest.java b/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerLoginListenerTest.java index 37ba5f0e..3bc672cf 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerLoginListenerTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerLoginListenerTest.java @@ -25,8 +25,8 @@ class PlayerLoginListenerTest extends CygnusPlayerTestBase { - private static GameConfig lobbyConfig() { - return GameConfig.builder().lobbyTime(30).minPlayers(2).gameTime(600).maxPlayers(10).build(); + private static GameConfig.Round lobbyConfig() { + return new GameConfig.Round(2, 10, 30, 600); } @Test @@ -77,14 +77,7 @@ void testSuccessfulLoginSendsResourcePackWhenPresent(@NotNull Env env) { Instance instance = env.createFlatInstance(); Player player = env.createPlayer(instance); - GameConfig packConfig = GameConfig.builder() - .lobbyTime(30) - .minPlayers(2) - .gameTime(600) - .maxPlayers(10) - .resourcePackUrl(URI.create("https://example.com/pack.zip")) - .resourcePackSha1("a".repeat(40)) - .build(); + GameConfig.ResourcePack packConfig = new GameConfig.ResourcePack(URI.create("https://example.com/pack.zip"), "a".repeat(40)); Optional resourcePackService = ResourcePackService.create(packConfig); assertTrue(resourcePackService.isPresent()); diff --git a/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerSpawnListenerTest.java b/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerSpawnListenerTest.java index 5c187f08..a2dd96ca 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerSpawnListenerTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/listener/PlayerSpawnListenerTest.java @@ -25,8 +25,8 @@ class PlayerSpawnListenerTest extends CygnusPlayerTestBase { - private static GameConfig lobbyConfig() { - return GameConfig.builder().lobbyTime(30).minPlayers(2).gameTime(600).maxPlayers(10).build(); + private static GameConfig.Round lobbyConfig() { + return new GameConfig.Round(2, 10, 30, 600); } @Test diff --git a/game/src/test/java/net/onelitefeather/cygnus/listener/game/GameStartListenerTest.java b/game/src/test/java/net/onelitefeather/cygnus/listener/game/GameStartListenerTest.java index d1b18a78..c254887a 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/listener/game/GameStartListenerTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/listener/game/GameStartListenerTest.java @@ -83,7 +83,7 @@ private static GameStartListener createListener(Env env, Instance instance) { GameConfig gameConfig = new GameConfigReader(Paths.get("")).getConfig(); TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() {}; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); Team slenderTeam = teamService.getTeam(GameConfig.SLENDER_KEY).orElseThrow(); Team survivorTeam = teamService.getTeam(GameConfig.SURVIVOR_KEY).orElseThrow(); @@ -94,7 +94,7 @@ private static GameStartListener createListener(Env env, Instance instance) { StaminaService staminaService = new StaminaService(); PageProvider pageProvider = new PageProvider(); PageProximityService pageProximityService = new PageProximityService( - gameConfig, + gameConfig.pageProximity(), survivorTeam::getPlayers, List::of ); diff --git a/game/src/test/java/net/onelitefeather/cygnus/noise/SlenderStaticServiceTest.java b/game/src/test/java/net/onelitefeather/cygnus/noise/SlenderStaticServiceTest.java index 7f11ee4d..78797daf 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/noise/SlenderStaticServiceTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/noise/SlenderStaticServiceTest.java @@ -217,14 +217,7 @@ void aResourcePackSoundIsSentAsNamed(@NotNull Env env) { TestConnection connection = env.createConnection(); Player slender = connection.connect(instance); Key packSound = Key.key("cygnus", "vhs_static"); - GameConfig config = GameConfig.builder() - .slenderStaticEnabled(true) - .slenderStaticSound(packSound) - .slenderStaticQuietInterval(QUIET_INTERVAL) - .slenderStaticFranticInterval(FRANTIC_INTERVAL) - .slenderStaticMinVolume(MIN_VOLUME) - .slenderStaticMaxVolume(MAX_VOLUME) - .build(); + GameConfig.SlenderStatic config = new GameConfig.SlenderStatic(true, packSound, QUIET_INTERVAL, FRANTIC_INTERVAL, MIN_VOLUME, MAX_VOLUME); SlenderStaticService service = service(config, () -> slender); service.registerListener(env.process().eventHandler()); EventDispatcher.call(new GameStartEvent()); @@ -257,7 +250,7 @@ private static void tick(SlenderStaticService service, int seconds) { * @param slender supplies the current slender * @return the service under test */ - private static SlenderStaticService service(GameConfig config, Supplier slender) { + private static SlenderStaticService service(GameConfig.SlenderStatic config, Supplier slender) { return new SlenderStaticService(config, slender); } @@ -267,14 +260,7 @@ private static SlenderStaticService service(GameConfig config, Supplier * @param enabled whether the static is on * @return the configuration */ - private static GameConfig config(boolean enabled) { - return GameConfig.builder() - .slenderStaticEnabled(enabled) - .slenderStaticSound(Key.key("weather.rain")) - .slenderStaticQuietInterval(QUIET_INTERVAL) - .slenderStaticFranticInterval(FRANTIC_INTERVAL) - .slenderStaticMinVolume(MIN_VOLUME) - .slenderStaticMaxVolume(MAX_VOLUME) - .build(); + private static GameConfig.SlenderStatic config(boolean enabled) { + return new GameConfig.SlenderStatic(enabled, Key.key("weather.rain"), QUIET_INTERVAL, FRANTIC_INTERVAL, MIN_VOLUME, MAX_VOLUME); } } diff --git a/game/src/test/java/net/onelitefeather/cygnus/page/PageProximityServiceTest.java b/game/src/test/java/net/onelitefeather/cygnus/page/PageProximityServiceTest.java index 912f8bd3..65bb6ff3 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/page/PageProximityServiceTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/page/PageProximityServiceTest.java @@ -132,12 +132,7 @@ void testADisabledServiceNeverStartsItsTask(@NotNull Env env) { Player player = connection.connect(instance, PLAYER_POS); Collector sounds = connection.trackIncoming(SoundEffectPacket.class); - GameConfig config = GameConfig.builder() - .pageProximityEnabled(false) - .pageProximityRange(20) - .pageProximityInterval(20) - .pageProximitySound(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND) - .build(); + GameConfig.PageProximity config = new GameConfig.PageProximity(false, 20, GameConfig.PageProximity.DEFAULT_SOUND, GameConfig.PageProximity.DEFAULT_VOLUME_FACTOR); PageProximityService service = service(config, player, new Pos(0, 64, 1)); service.startTask(); @@ -157,12 +152,7 @@ void testAnUnknownSoundKeyFallsBackToTheDefaultChime(@NotNull Env env) { Player player = connection.connect(instance, PLAYER_POS); Collector sounds = connection.trackIncoming(SoundEffectPacket.class); - GameConfig config = GameConfig.builder() - .pageProximityEnabled(true) - .pageProximityRange(20) - .pageProximityInterval(20) - .pageProximitySound(Key.key("cygnus", "no_such_sound")) - .build(); + GameConfig.PageProximity config = new GameConfig.PageProximity(true, 20, Key.key("cygnus", "no_such_sound"), GameConfig.PageProximity.DEFAULT_VOLUME_FACTOR); service(config, player, new Pos(0, 64, 1)).tick(); @@ -189,23 +179,12 @@ void testTheTaskRunsWhileTheServiceIsEnabled(@NotNull Env env) { } /** A configuration on the shipped volume factor. */ - private static GameConfig config(int range) { - return GameConfig.builder() - .pageProximityEnabled(true) - .pageProximityRange(range) - .pageProximityInterval(20) - .pageProximitySound(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND) - .build(); + private static GameConfig.PageProximity config(int range) { + return new GameConfig.PageProximity(true, range, GameConfig.PageProximity.DEFAULT_SOUND, GameConfig.PageProximity.DEFAULT_VOLUME_FACTOR); } - private static GameConfig config(int range, float volumeFactor) { - return GameConfig.builder() - .pageProximityEnabled(true) - .pageProximityRange(range) - .pageProximityInterval(20) - .pageProximitySound(GameConfig.DEFAULT_PAGE_PROXIMITY_SOUND) - .pageProximityVolumeFactor(volumeFactor) - .build(); + private static GameConfig.PageProximity config(int range, float volumeFactor) { + return new GameConfig.PageProximity(true, range, GameConfig.PageProximity.DEFAULT_SOUND, volumeFactor); } @Test @@ -254,7 +233,7 @@ void testPhase3CriticalChimeBelowTwentyPercent(@NotNull Env env) { sounds.assertSingle(packet -> { assertEquals(PageProximityService.CRITICAL_PITCH, packet.pitch(), 0.001F); - float baseVolume = (20 / 16.0F) * GameConfig.DEFAULT_PAGE_PROXIMITY_VOLUME_FACTOR; + float baseVolume = (20 / 16.0F) * GameConfig.PageProximity.DEFAULT_VOLUME_FACTOR; assertEquals(baseVolume * PageProximityService.CRITICAL_VOLUME_MULTIPLIER, packet.volume(), 0.001F); }); env.destroyInstance(instance, true); @@ -277,7 +256,7 @@ void testChimeIntervalThrottling(@NotNull Env env) { env.destroyInstance(instance, true); } - private static PageProximityService service(GameConfig config, Player player, Pos... pages) { + private static PageProximityService service(GameConfig.PageProximity config, Player player, Pos... pages) { Collection listeners = List.of(player); List targets = Arrays.stream(pages) .map(pos -> PageProximityTarget.of(UUID.nameUUIDFromBytes(pos.toString().getBytes()), pos, 0.35)) diff --git a/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhaseCountdownSoundIntegrationTest.java b/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhaseCountdownSoundIntegrationTest.java index c11f6fb9..cc5cd87e 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhaseCountdownSoundIntegrationTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhaseCountdownSoundIntegrationTest.java @@ -27,12 +27,7 @@ class LobbyPhaseCountdownSoundIntegrationTest extends CygnusPlayerTestBase { void testLobbyPhasePlaysCountdownSoundAtTenSeconds(@NotNull Env env) { Instance instance = env.createFlatInstance(); - GameConfig config = GameConfig.builder() - .lobbyTime(30) - .minPlayers(1) - .gameTime(600) - .maxPlayers(10) - .build(); + GameConfig.Round config = new GameConfig.Round(1, 10, 30, 600); LobbyPhase lobbyPhase = new LobbyPhase(config, () -> instance); @@ -52,12 +47,7 @@ void testLobbyPhasePlaysCountdownSoundAtTenSeconds(@NotNull Env env) { void testLobbyPhasePlaysNoCountdownSoundOutsideMarks(@NotNull Env env) { Instance instance = env.createFlatInstance(); - GameConfig config = GameConfig.builder() - .lobbyTime(30) - .minPlayers(1) - .gameTime(600) - .maxPlayers(10) - .build(); + GameConfig.Round config = new GameConfig.Round(1, 10, 30, 600); LobbyPhase lobbyPhase = new LobbyPhase(config, () -> instance); diff --git a/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhasePlayerThresholdIntegrationTest.java b/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhasePlayerThresholdIntegrationTest.java index f78b96f2..75f6d322 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhasePlayerThresholdIntegrationTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/phase/LobbyPhasePlayerThresholdIntegrationTest.java @@ -31,12 +31,7 @@ class LobbyPhasePlayerThresholdIntegrationTest extends CygnusPlayerTestBase { void testLobbyPhaseStateTransitionsAndWaitingDisplay(@NotNull Env env) { Instance instance = env.createFlatInstance(); - GameConfig config = GameConfig.builder() - .lobbyTime(30) - .minPlayers(2) - .gameTime(600) - .maxPlayers(10) - .build(); + GameConfig.Round config = new GameConfig.Round(2, 10, 30, 600); LobbyPhase lobbyPhase = new LobbyPhase(config); diff --git a/game/src/test/java/net/onelitefeather/cygnus/phase/task/LobbyTimeTransitionTaskTest.java b/game/src/test/java/net/onelitefeather/cygnus/phase/task/LobbyTimeTransitionTaskTest.java index 24077545..45615d76 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/phase/task/LobbyTimeTransitionTaskTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/phase/task/LobbyTimeTransitionTaskTest.java @@ -74,12 +74,7 @@ void testLobbyPhaseTriggersTimeTransitionAtTenSeconds(@NotNull Env env) { Instance instance = env.createFlatInstance(); instance.setTime(1000L); - GameConfig config = GameConfig.builder() - .lobbyTime(30) - .minPlayers(1) - .gameTime(600) - .maxPlayers(10) - .build(); + GameConfig.Round config = new GameConfig.Round(1, 10, 30, 600); LobbyPhase lobbyPhase = new LobbyPhase(config, () -> instance); lobbyPhase.setCurrentTicks(10); diff --git a/game/src/test/java/net/onelitefeather/cygnus/resourcepack/ResourcePackServiceTest.java b/game/src/test/java/net/onelitefeather/cygnus/resourcepack/ResourcePackServiceTest.java index 2c78b35d..ca7f169b 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/resourcepack/ResourcePackServiceTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/resourcepack/ResourcePackServiceTest.java @@ -38,15 +38,8 @@ class ResourcePackServiceTest extends CygnusPlayerTestBase { private static final URI PACK_URL = URI.create("https://example.com/pack.zip"); private static final String PACK_HASH = "a".repeat(40); - private static GameConfig config(@Nullable URI url, @Nullable String sha1) { - return GameConfig.builder() - .minPlayers(2) - .maxPlayers(10) - .lobbyTime(30) - .gameTime(600) - .resourcePackUrl(url) - .resourcePackSha1(sha1) - .build(); + private static GameConfig.ResourcePack config(@Nullable URI url, @Nullable String sha1) { + return new GameConfig.ResourcePack(url, sha1); } private ResourcePackService createService() { diff --git a/game/src/test/java/net/onelitefeather/cygnus/team/TeamRosterTest.java b/game/src/test/java/net/onelitefeather/cygnus/team/TeamRosterTest.java index 87121e9d..33641c72 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/team/TeamRosterTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/team/TeamRosterTest.java @@ -51,7 +51,7 @@ private static TeamService teamsWithoutPlayers() { TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() { }; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); return teamService; } diff --git a/game/src/test/java/net/onelitefeather/cygnus/utils/TeamHelperTest.java b/game/src/test/java/net/onelitefeather/cygnus/utils/TeamHelperTest.java index a809560d..d53632f8 100644 --- a/game/src/test/java/net/onelitefeather/cygnus/utils/TeamHelperTest.java +++ b/game/src/test/java/net/onelitefeather/cygnus/utils/TeamHelperTest.java @@ -110,7 +110,7 @@ void testSlenderTeleport(@NotNull Env env) { TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() { }; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); Pos slenderSpawn = new Pos(10, 10, 10); GameMap gameMap = new GameMap("Test", Pos.ZERO, slenderSpawn, Set.of(), Set.of(), List.of(), null); assertNotNull(gameMap); @@ -140,7 +140,7 @@ void testInvalidUpdateTabList() { TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() { }; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); IllegalStateException exception = assertThrows( IllegalStateException.class, @@ -155,7 +155,7 @@ void testUpdateTabList(@NotNull Env env) { TeamService teamService = TeamService.of(); TeamCreator teamCreator = new TeamCreator() { }; - teamCreator.createTeams(gameConfig, teamService); + teamCreator.createTeams(gameConfig.teams(), teamService); Set survivors = new HashSet<>();