Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,48 @@ public sealed interface GameConfig permits GameConfigImpl, InternalGameConfig {

int MIN_ACTIVE_PAGE_COUNT = 4 * 2;

/**
* How many seconds after a round starts before the first pages spawn.
* <p>
* Spawning immediately at {@code GameStartEvent} let survivors grab a page before they had even
* moved from the spawn point. The delay gives them time to spread across the map first.
* </p>
*
* @since 2.15.0
*/
int PAGE_SPAWN_DELAY = 10;

/**
* How many seconds {@link #PAGE_SPAWN_DELAY} may randomly shift up or down, re-rolled every
* round.
* <p>
* 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.
* </p>
*
* @since 2.15.0
*/
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.
* <p>
* The page then has to come back on the spot it was found on. Without the delay it would be
* collectible again right away, letting a survivor pick up page after page on the same spot.
* </p>
*
* @since 2.15.0
*/
int PAGE_RESPAWN_DELAY = 15;

/**
* How many seconds {@link #PAGE_RESPAWN_DELAY} may randomly shift up or down, re-rolled for every
* hidden page, so waiting next to the spot does not pay off.
*
* @since 2.15.0
*/
int PAGE_RESPAWN_DELAY_JITTER = 5;

int PAGE_TTL_TIME = 60;

int FORCE_START_TIME = 11;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import net.minestom.server.MinecraftServer;
import net.onelitefeather.cygnus.common.config.GameConfig;

import java.util.concurrent.ThreadLocalRandom;

/**
* Utility class for calculating the number of pages allocated for the dynamic page system.
* The page count is based on the number of current online players.
Expand All @@ -17,23 +19,69 @@ public final class PageCalculation {
private static final int PLAYER_SIZE_FOR_DYNAMIC_PAGE_ALLOCATION = 4;
private static final int PAGE_COUNT_MULTIPLIER = 2;

/**
* The largest amount {@link #calculatePageAmount()} may add on top of the base amount, so the
* total can't be worked out in advance from the player count alone.
* <p>
* Not exposed through {@link GameConfig}: unlike the other page settings, this one is
* deliberately not something a server operator should be able to turn off or tune.
* </p>
*/
private static final int PAGE_COUNT_JITTER_MAX = 2;

private static final int PLAYER_SIZE_FOR_ACTIVE_PAGE_SCALING = 8;

/**
* How many extra survivors above {@value #PLAYER_SIZE_FOR_ACTIVE_PAGE_SCALING} it takes for
* {@link #calculateActivePageAmount()} to add one more page.
* <p>
* Playtesting a steeper step (one active page per extra survivor) reached 16 concurrently
* active pages, which felt like clutter; this keeps the top of the range below 10.
* </p>
*/
private static final int ACTIVE_PAGE_COUNT_STEP_SIZE = 3;

/**
* Calculates the number of pages to allocate for the dynamic page system.
* <p>
* If the number of online players (excluding one) is less than {@value #PLAYER_SIZE_FOR_DYNAMIC_PAGE_ALLOCATION},
* {@link GameConfig#MIN_PAGE_COUNT} is returned. Otherwise, the page count is determined by
* multiplying the adjusted player count by {@value #PAGE_COUNT_MULTIPLIER}.
* the base amount is {@link GameConfig#MIN_PAGE_COUNT}. Otherwise, it is the adjusted player
* count multiplied by {@value #PAGE_COUNT_MULTIPLIER}. A random amount between 0 and
* {@value #PAGE_COUNT_JITTER_MAX} is then added on top, re-rolled every call, so the total
* can't be derived from the player count alone.
*
* @return the number of pages to allocate, at least {@link GameConfig#MIN_PAGE_COUNT}
*/
public static int calculatePageAmount() {
int currentPlayers = MinecraftServer.getConnectionManager().getOnlinePlayers().size();

if (currentPlayers - 1 < PLAYER_SIZE_FOR_DYNAMIC_PAGE_ALLOCATION) {
return GameConfig.MIN_PAGE_COUNT;
} else {
return (currentPlayers - 1) * PAGE_COUNT_MULTIPLIER;
int baseAmount = currentPlayers - 1 < PLAYER_SIZE_FOR_DYNAMIC_PAGE_ALLOCATION
? GameConfig.MIN_PAGE_COUNT
: (currentPlayers - 1) * PAGE_COUNT_MULTIPLIER;

return baseAmount + ThreadLocalRandom.current().nextInt(PAGE_COUNT_JITTER_MAX + 1);
}

/**
* Calculates how many pages should be concurrently active (spawned in the world at once).
* <p>
* This stays below {@link #calculatePageAmount()} on purpose: it governs how many pages exist
* in the world at the same time, not the total pool a round draws from. If the number of online
* players (excluding one) is less than {@value #PLAYER_SIZE_FOR_ACTIVE_PAGE_SCALING},
* {@link GameConfig#MIN_ACTIVE_PAGE_COUNT} is returned. Otherwise, one page is added for every
* {@value #ACTIVE_PAGE_COUNT_STEP_SIZE} survivors past that threshold, so the count stays flat
* for most of the range and only rises near the top of a full lobby.
*
* @return the number of pages to keep active at once, at least {@link GameConfig#MIN_ACTIVE_PAGE_COUNT}
*/
public static int calculateActivePageAmount() {
int currentPlayers = MinecraftServer.getConnectionManager().getOnlinePlayers().size();
int survivors = currentPlayers - 1;

if (survivors < PLAYER_SIZE_FOR_ACTIVE_PAGE_SCALING) {
return GameConfig.MIN_ACTIVE_PAGE_COUNT;
}
return GameConfig.MIN_ACTIVE_PAGE_COUNT + (survivors - PLAYER_SIZE_FOR_ACTIVE_PAGE_SCALING) / ACTIVE_PAGE_COUNT_STEP_SIZE;
}

private PageCalculation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import net.onelitefeather.cygnus.common.config.GameConfig;
import net.onelitefeather.cygnus.common.page.event.PageExpiredEvent;
import net.onelitefeather.cygnus.common.util.Helper;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -48,18 +47,17 @@ public final class PageEntity extends Entity implements PageCreator, PageProximi
private boolean send;
private boolean interactable = true;
private int initialBlockLight;
private @Nullable PageResource resource;
private PageResource resource;

/**
* Constructs a new {@link PageEntity}.
* Constructs a new {@link PageEntity}. The page is not part of any instance until {@link #place(Instance)} is called.
*
* @param instance the instance where the entity should spawn
* @param spawnPos the position where the entity should spawn
* @param resource the spot the page stands on
* @param pageCount the current page count
*/
PageEntity(Instance instance, Pos spawnPos, int pageCount) {
PageEntity(PageResource resource, int pageCount) {
super(EntityType.ITEM_DISPLAY);
this.setInstance(instance, spawnPos);
this.resource = resource;
this.hitBox = new Entity(EntityType.INTERACTION);
this.pageItem = createPageItem(pageCount);
this.ttlTime = Helper.calculateOffsetTime(GameConfig.PAGE_TTL_TIME);
Expand All @@ -86,7 +84,6 @@ public final class PageEntity extends Entity implements PageCreator, PageProximi
interactionMeta.setResponse(true);
interactionMeta.setNotifyAboutChanges(true);
interactionMeta.setHasGlowingEffect(true);
this.hitBox.setInstance(instance, spawnPos.sub(HALF_BLOCK));
this.hitBox.setAutoViewable(true);
this.hitBox.setTag(Tags.PAGE_TAG, this.hitBox.getUuid());
}
Expand All @@ -108,6 +105,16 @@ public void disableInteraction() {
this.interactable = false;
}

/**
* Takes the page out of play for the given time. It comes back on its current spot afterwards.
*
* @param seconds how long the page stays hidden
*/
void hideFor(int seconds) {
this.disableInteraction();
this.ttlTime = seconds;
}

/**
* Updates the item stack for the page.
*
Expand Down Expand Up @@ -164,6 +171,11 @@ public void tick(long time) {
}

if (currentTickTime >= ttlTime) {
// A page is only out of play while it waits to come back, see hideFor
if (!this.interactable) {
this.enableInteraction();
return;
}
this.disableInteraction();
send = true;
EventDispatcher.call(new PageExpiredEvent(this));
Expand Down Expand Up @@ -240,20 +252,35 @@ public UUID getHitBoxUUID() {
}

/**
* Sets the {@link PageResource} the page currently stands on.
* Places the page and its hit box into the given instance, on the spot of its resource.
*
* @param instance the instance to place the page in
* @return a future that completes once both entities are in the instance
*/
public CompletableFuture<Void> place(Instance instance) {
Pos position = Helper.updatePosition(this.resource.position().asPos(), this.resource.face());
return CompletableFuture.allOf(
this.hitBox.setInstance(instance, position.sub(HALF_BLOCK)),
this.setInstance(instance, position)
);
}

/**
* Moves the already placed page to the spot of the given resource.
*
* @param resource the resource, or {@code null} once the page has no spot of its own
* @param resource the spot to move to
*/
void setResource(@Nullable PageResource resource) {
void moveTo(PageResource resource) {
this.resource = resource;
this.teleport(Helper.updatePosition(resource.position().asPos(), resource.face()));
}

/**
* Returns the {@link PageResource} the page currently stands on.
*
* @return the resource, or {@code null} if none is set
* @return the resource
*/
@Nullable PageResource getResource() {
PageResource getResource() {
return this.resource;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package net.onelitefeather.cygnus.common.page;

import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
Expand All @@ -20,21 +18,15 @@ private PageFactory() {}

/**
* Creates a new reference from a {@link PageEntity} class.
* @param instance the instance where the entity should be spawned
* @param spawnPos the position to spawn
* @param direction the direction for the spawning
* @param resource the spot the page should stand on
* @param pageCount the current count of the page
* @return the created entity reference
*/
@Contract(value = "_, _, _, _ -> new" , pure = true)
public static PageEntity createPage(
Instance instance,
Pos spawnPos,
Direction direction,
int pageCount
) {
@Contract(value = "_, _ -> new" , pure = true)
public static PageEntity createPage(PageResource resource, int pageCount) {
Direction direction = resource.face();
Check.argCondition(direction == Direction.UP || direction == Direction.DOWN, "The direction " + direction + " is not supported");
Check.argCondition(pageCount < 1, "The page count can't be zero or negative");
return new PageEntity(instance, spawnPos, pageCount);
return new PageEntity(resource, pageCount);
}
}
Loading
Loading