diff --git a/README.md b/README.md
index e1d8811..ca25e62 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,75 @@
-## Features
-For a complete list of features please check the mods [official page](https://www.curseforge.com/minecraft/mc-mods/fabric-seasons)
+## Description
+
+Fabric Seasons adds four seasons to Minecraft, each lasting 28 in-game days (configurable). The current season is defined by the world time (using `/time set 0` will reset to day 1 of Spring). Each season has its own changes. Spring will match the original biome colors and biome behaviors.
+
+The mod has 2 components:
+
+- Server: If installed on the server, biomes will have different temperatures. This enables weather changes like snowing and block freezing in the winter. The server component also contains seasonal crops, allowing crops to grow at different speeds according to the season.
+- Client: If installed on the client, biomes will have different colormaps according to the current season. This is just a visual change and it will not physically impact your world in any form.
+
+To get the full experience, it is recommended that you install the mod on both the server and the client.
+
+All biomes are grouped in 5 categories with the following characteristics:
+
+- Permanently Frozen Biomes (`temp ≤ -0.51`): Permanently frozen year-round
+- Usually Frozen Biomes (`temp ≤ 0.15`): Becomes ice free in the Summer
+- Temperate Biomes (`temp ≤ 0.49`): Ice free in the Spring and Summer
+- Usually Ice Free Biomes (`temp ≤ 0.79`): Only freezes in the Winter
+- Ice Free Biomes (`temp > 0.79`): Ice free year-round, will also allow rain in the Winter
+
+
+
+
+
+
+
+Only overworld biomes can have this seasonality, which can be configured in `config/seasons.json`
+
+*Please note that this mod melts / applies freezing blocks and snow **within simulation distance**. This means that anything outside is unaffected by the seasons unless if you travel to these blocks. It is currently not possible to globally melt or freeze blocks outside of this distance. It is possible to mitigate this by temporarily increasing the `randomTickSpeed`. Otherwise if you want to completely avoid this limitation, set `doTemperatureChanges` in the config file to `false`.*
+
+## Configurations
+
+Fabric Seasons can be configured in your instance's `config` folder. The file is called `seasons.json`. To apply your own changes to the configuration, please restart your client.
+| Config Variable | Description | Default
+| -------- | ------- | -------
+| springLength / summerLength / fallLength / winterLength | The integer for the length of a season in "game ticks" (672000 game ticks == 33600 in-game seconds == 28 in-game days) | 672000
+| startingSeason | The starting season of the year. Only applicable when `isSeasonLocked` is `false` and `isSeasonTiedWithSystemTime` is `false`. Valid values are `SPRING`, `SUMMER`, `FALL`, or `WINTER`. | "SPRING"
+| isSeasonLocked | Flag to lock the season | false
+| lockedSeason | The season to lock if `isSeasonLocked` is true. Valid values are `SPRING`, `SUMMER`, `FALL`, or `WINTER`.| "SPRING"
+| dimensionAllowList | The list of dimensions to allow seasons. This may be useful for multiplayer. A valid value would be one in the format of `":"`. | ["minecraft:overworld"]
+| doTemperatureChanges | Flag that modifies precipitation and freezing on biomes. **Setting to false essentially disables any biome behavior or aesthetic changes year round.** | true
+| shouldSnowyBiomesMeltInSummer | Flag that determines if "Usually Freezing Biomes" should melt during Summer | true
+| shouldIceNearWaterMelt | When true, ice will melt in an ice-free biome regardless of being placed by a player when it is beside a water source or flowing water. When false, default ice behavior occurs. | false
+| biomeDenyList | The list of biomes that keep it's original temperature and freezing behavior year-round. A valid value would be one in the format of `":"`. | ["terralith:glacial_chasm", "minecraft:frozen_ocean", "minecraft:deep_frozen_ocean", "minecraft:ocean", "minecraft:deep_ocean", "minecraft:cold_ocean", "minecraft:deep_cold_ocean", "minecraft:lukewarm_ocean", "minecraft:deep_lukewarm_ocean", "minecraft:warm_ocean"]
+| biomeForceSnowInWinterList | The list of biomes to force freezing during the winter season. A valid value would be one in the format of `":"`. | ["minecraft:plains", "minecraft:sunflower_plains", "minecraft:stony_peaks"]
+| isSeasonTiedWithSystemTime | Flag that determines if the seasons should be synced to real world seasonal cycles | false
+| isInNorthHemisphere | Only used if `isSeasonTiedWithSystemTime` is set to true. Determines if the player is situated in the North Hemisphere to properly calculate the seasons. | true
+| isFallAndSpringReversed | By default, there are more biomes that snow over during Fall. Setting this to true will reverse it, so that more biomes snow over in the Spring compared to Fall. | false
+| isSeasonMessingCrops | Flag that determines if the seasons should change the default growth rate | true
+| isSeasonMessingBonemeal | Flag that determines if the seasons should change the default bonemeal behavior | false
+| doCropsGrowNormallyUnderground | Flag that determines if crops underground should have default behavior. Underground is considered to be sky light level of 0. | false
+| doAnimalsBreedInWinter | Flag that determines if animals should be able to breed during the winter | true
+| notifyCompat | Flag that determines if a message should be sent when a mod has a compatibility addon and you do not have it installed | true
+| debugCommandEnabled | Only useful in development environments | false
+
+## Crop Growth
+
+Crops will grow at **different speeds** depending on the current season.
+
+
+
+
+
+Each **individual crop** will have a **pre-configured growth speed** for one of the four seasons, this is controlled by a datapack and can be changed by the user if they want.
+
+
+
+
+
+Please refer to online tutorials on **how to make a datapack**. Make sure that your `pack_format` is of the correct version for your particular Minecraft Version. If you are lost, there is an example datapack found in the **pinned messages** of the Discord channel.
## Description
diff --git a/gradle.properties b/gradle.properties
index 7dfaf84..09bd810 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ modrinth_version=2.8.7
github_api_version=1.314
# Mod Properties
-mod_version=2.4.1-BETA+1.21
+mod_version=2.4.2-BETA+1.21
maven_group=io.github.lucaargolo
# Fabric Properties
diff --git a/src/main/java/io/github/lucaargolo/seasons/FabricSeasons.java b/src/main/java/io/github/lucaargolo/seasons/FabricSeasons.java
index 17e5318..7cb6a30 100644
--- a/src/main/java/io/github/lucaargolo/seasons/FabricSeasons.java
+++ b/src/main/java/io/github/lucaargolo/seasons/FabricSeasons.java
@@ -154,38 +154,50 @@ public static ReplacedMeltablesState getReplacedMeltablesState(ServerWorld world
}
public static long getTimeToNextSeason(World world) {
- return getTimeToNextSeason(world, false);
- }
-
- public static long getTimeToNextSeason(World world, boolean ignoreDimension) {
+ long springLength = CONFIG.getSpringLength();
+ long summerLength = CONFIG.getSummerLength();
+ long fallLength = CONFIG.getFallLength();
+ long winterLength = CONFIG.getWinterLength();
RegistryKey dimension = world.getRegistryKey();
- if ((ignoreDimension || CONFIG.isValidInDimension(dimension)) && !CONFIG.isSeasonLocked()) {
+ if (CONFIG.isValidInDimension(dimension) && !CONFIG.isSeasonLocked()) {
if(CONFIG.isSeasonTiedWithSystemTime()) {
return getTimeToNextSystemSeason() * 24000;
}
- long springTime = world.getTimeOfDay() % CONFIG.getYearLength();
- long summerTime = springTime - CONFIG.getSpringLength();
- long fallTime = summerTime - CONFIG.getSummerLength();
- long winterTime = fallTime - CONFIG.getFallLength();
-
- long seasonTime = switch (getCurrentSeason(world)) {
- case SPRING -> springTime;
- case SUMMER -> summerTime;
- case FALL -> fallTime;
- case WINTER -> winterTime;
+
+ Season currentSeason = getCurrentSeason(world);
+
+ long[] seasonLengthArray = new long[]{springLength, summerLength, fallLength, winterLength};
+ Season[] seasonArray = new Season[]{Season.SPRING, Season.SUMMER, Season.FALL, Season.WINTER};
+
+ int startSeasonIndex = switch (CONFIG.getStartingSeason()) {
+ case SPRING -> 0;
+ case SUMMER -> 1;
+ case FALL -> 2;
+ case WINTER -> 3;
};
- return getCurrentSeason(world).getSeasonLength() - seasonTime;
+
+ long season1LimitYTD = seasonLengthArray[startSeasonIndex];
+ long season2LimitYTD = season1LimitYTD + seasonLengthArray[(startSeasonIndex + 1) % 4];
+ long season3LimitYTD = season2LimitYTD + seasonLengthArray[(startSeasonIndex + 2) % 4];
+ long yearLength = season3LimitYTD + seasonLengthArray[(startSeasonIndex + 3) % 4];
+ long timeOfYear = world.getTimeOfDay() % yearLength;
+
+ if(currentSeason == seasonArray[startSeasonIndex]) {
+ return season1LimitYTD - timeOfYear;
+ } else if(currentSeason == seasonArray[(startSeasonIndex + 1) % 4]) {
+ return season2LimitYTD - timeOfYear;
+ } else if (currentSeason == seasonArray[(startSeasonIndex + 2) % 4]) {
+ return season3LimitYTD - timeOfYear;
+ } else if (currentSeason == seasonArray[(startSeasonIndex + 3) % 4]) {
+ return yearLength - timeOfYear;
+ }
}
return Long.MAX_VALUE;
}
- public static Season getNextSeason(World world) {
- return getNextSeason(world, false);
- }
-
- public static Season getNextSeason(World world, boolean ignoreDimension) {
+ public static Season getNextSeason(World world, Season currentSeason) {
RegistryKey dimension = world.getRegistryKey();
- if (ignoreDimension || CONFIG.isValidInDimension(dimension)) {
+ if (CONFIG.isValidInDimension(dimension)) {
if(CONFIG.isSeasonLocked()) {
return CONFIG.getLockedSeason();
}
@@ -193,65 +205,53 @@ public static Season getNextSeason(World world, boolean ignoreDimension) {
return getCurrentSystemSeason().getNext();
}
- long springTime = world.getTimeOfDay() % CONFIG.getYearLength();
- long summerTime = springTime - CONFIG.getSpringLength();
- long fallTime = summerTime - CONFIG.getSummerLength();
- long winterTime = fallTime - CONFIG.getFallLength();
-
- long seasonTime = switch (getCurrentSeason(world)) {
- case SPRING -> CONFIG.getSpringLength() - springTime;
- case SUMMER -> CONFIG.getSummerLength() - summerTime;
- case FALL -> CONFIG.getFallLength() - fallTime;
- case WINTER -> CONFIG.getWinterLength() - winterTime;
+ return switch (getCurrentSeason(world)) {
+ case SPRING -> Season.SUMMER;
+ case SUMMER -> Season.FALL;
+ case FALL -> Season.WINTER;
+ case WINTER -> Season.SPRING;
};
-
- long worldTime = world.getTimeOfDay() + seasonTime;
-
- springTime = worldTime % CONFIG.getYearLength();
- summerTime = springTime - CONFIG.getSpringLength();
- fallTime = summerTime - CONFIG.getSummerLength();
- winterTime = fallTime - CONFIG.getFallLength();
-
- if(winterTime >= 0 && CONFIG.getWinterLength() > 0) {
- return Season.WINTER;
- }else if(fallTime >= 0 && CONFIG.getFallLength() > 0) {
- return Season.FALL;
- }else if(summerTime >= 0 && CONFIG.getSummerLength() > 0) {
- return Season.SUMMER;
- }else if(springTime >= 0 && CONFIG.getSpringLength() > 0) {
- return Season.SPRING;
- }
}
return Season.SPRING;
}
public static Season getCurrentSeason(World world) {
- return getCurrentSeason(world, false);
- }
-
- public static Season getCurrentSeason(World world, boolean ignoreDimension) {
+ long springLength = CONFIG.getSpringLength();
+ long summerLength = CONFIG.getSummerLength();
+ long fallLength = CONFIG.getFallLength();
+ long winterLength = CONFIG.getWinterLength();
RegistryKey dimension = world.getRegistryKey();
- if (ignoreDimension || CONFIG.isValidInDimension(dimension)) {
+ if (CONFIG.isValidInDimension(dimension)) {
if(CONFIG.isSeasonLocked()) {
return CONFIG.getLockedSeason();
- }
- if(CONFIG.isSeasonTiedWithSystemTime()) {
+ }else if(CONFIG.isSeasonTiedWithSystemTime()) {
return getCurrentSystemSeason();
- }
-
- long springTime = world.getTimeOfDay() % CONFIG.getYearLength();
- long summerTime = springTime - CONFIG.getSpringLength();
- long fallTime = summerTime - CONFIG.getSummerLength();
- long winterTime = fallTime - CONFIG.getFallLength();
-
- if(winterTime >= 0 && CONFIG.getWinterLength() > 0) {
- return Season.WINTER;
- }else if(fallTime >= 0 && CONFIG.getFallLength() > 0) {
- return Season.FALL;
- }else if(summerTime >= 0 && CONFIG.getSummerLength() > 0) {
- return Season.SUMMER;
- }else if(springTime >= 0 && CONFIG.getSpringLength() > 0) {
- return Season.SPRING;
+ }else if(CONFIG.isValidStartingSeason() && springLength >= 0 && summerLength >= 0 && fallLength >= 0 && winterLength >= 0) {
+ long[] seasonLengthArray = new long[]{springLength, summerLength, fallLength, winterLength};
+ Season[] seasonArray = new Season[]{Season.SPRING, Season.SUMMER, Season.FALL, Season.WINTER};
+
+ int startSeasonIndex = switch (CONFIG.getStartingSeason()) {
+ case SPRING -> 0;
+ case SUMMER -> 1;
+ case FALL -> 2;
+ case WINTER -> 3;
+ };
+
+ long season1LimitYTD = seasonLengthArray[startSeasonIndex];
+ long season2LimitYTD = season1LimitYTD + seasonLengthArray[(startSeasonIndex + 1) % 4];
+ long season3LimitYTD = season2LimitYTD + seasonLengthArray[(startSeasonIndex + 2) % 4];
+ long yearLength = season3LimitYTD + seasonLengthArray[(startSeasonIndex + 3) % 4];
+ long timeOfYear = world.getTimeOfDay() % yearLength;
+
+ if(timeOfYear < season1LimitYTD) {
+ return seasonArray[startSeasonIndex];
+ } else if(timeOfYear < season2LimitYTD) {
+ return seasonArray[(startSeasonIndex + 1) % 4];
+ } else if (timeOfYear < season3LimitYTD) {
+ return seasonArray[(startSeasonIndex + 2) % 4];
+ } else if (timeOfYear < yearLength) {
+ return seasonArray[(startSeasonIndex + 3) % 4];
+ }
}
}
return Season.SPRING;
diff --git a/src/main/java/io/github/lucaargolo/seasons/commands/SeasonCommand.java b/src/main/java/io/github/lucaargolo/seasons/commands/SeasonCommand.java
index 115000c..2a16c0e 100644
--- a/src/main/java/io/github/lucaargolo/seasons/commands/SeasonCommand.java
+++ b/src/main/java/io/github/lucaargolo/seasons/commands/SeasonCommand.java
@@ -16,23 +16,55 @@ public static void register(CommandDispatcher dispatcher) {
dispatcher.register(CommandManager.literal("season")
.then(CommandManager.literal("set").requires((source) -> source.hasPermissionLevel(2))
.then(CommandManager.literal("spring")
- .executes(context -> TimeCommand.executeSet(context.getSource(), 0))
+ .executes(
+ context -> TimeCommand.executeSet(context.getSource(),
+ switch(FabricSeasons.CONFIG.getStartingSeason()) {
+ case SPRING -> 0;
+ case WINTER -> FabricSeasons.CONFIG.getWinterLength();
+ case FALL -> FabricSeasons.CONFIG.getFallLength() + FabricSeasons.CONFIG.getWinterLength();
+ case SUMMER -> FabricSeasons.CONFIG.getSummerLength() + FabricSeasons.CONFIG.getFallLength() + FabricSeasons.CONFIG.getWinterLength();
+ }
+ ))
)
.then(CommandManager.literal("summer")
- .executes(context -> TimeCommand.executeSet(context.getSource(), FabricSeasons.CONFIG.getSpringLength()))
+ .executes(context -> TimeCommand.executeSet(
+ context.getSource(),
+ switch(FabricSeasons.CONFIG.getStartingSeason()) {
+ case SUMMER -> 0;
+ case SPRING -> FabricSeasons.CONFIG.getSpringLength();
+ case WINTER -> FabricSeasons.CONFIG.getWinterLength() + FabricSeasons.CONFIG.getSpringLength();
+ case FALL -> FabricSeasons.CONFIG.getFallLength() + FabricSeasons.CONFIG.getWinterLength() + FabricSeasons.CONFIG.getSpringLength();
+ }
+ ))
)
.then(CommandManager.literal("fall")
- .executes(context -> TimeCommand.executeSet(context.getSource(), FabricSeasons.CONFIG.getSpringLength() + FabricSeasons.CONFIG.getSummerLength()))
+ .executes(context -> TimeCommand.executeSet(
+ context.getSource(),
+ switch(FabricSeasons.CONFIG.getStartingSeason()) {
+ case FALL -> 0;
+ case SUMMER -> FabricSeasons.CONFIG.getSummerLength();
+ case SPRING -> FabricSeasons.CONFIG.getSpringLength() + FabricSeasons.CONFIG.getSummerLength();
+ case WINTER -> FabricSeasons.CONFIG.getWinterLength() + FabricSeasons.CONFIG.getSpringLength() + FabricSeasons.CONFIG.getSummerLength();
+ }
+ ))
)
.then(CommandManager.literal("winter")
- .executes(context -> TimeCommand.executeSet(context.getSource(), FabricSeasons.CONFIG.getSpringLength() + FabricSeasons.CONFIG.getSummerLength() + FabricSeasons.CONFIG.getFallLength()))
+ .executes(context -> TimeCommand.executeSet(
+ context.getSource(),
+ switch(FabricSeasons.CONFIG.getStartingSeason()) {
+ case WINTER -> 0;
+ case FALL -> FabricSeasons.CONFIG.getFallLength();
+ case SUMMER -> FabricSeasons.CONFIG.getSummerLength() + FabricSeasons.CONFIG.getFallLength();
+ case SPRING -> FabricSeasons.CONFIG.getSpringLength() + FabricSeasons.CONFIG.getSummerLength() + FabricSeasons.CONFIG.getFallLength();
+ }
+ ))
)
)
.then(CommandManager.literal("query")
.executes(context -> {
World world = context.getSource().getWorld();
Season currentSeason = FabricSeasons.getCurrentSeason(world);
- Season nextSeason = FabricSeasons.getNextSeason(world);
+ Season nextSeason = FabricSeasons.getNextSeason(world, currentSeason);
long ticksLeft = FabricSeasons.getTimeToNextSeason(world);
context.getSource().sendFeedback(() -> Text.translatable("commands.seasons.query_1",
Text.translatable(currentSeason.getTranslationKey()).formatted(currentSeason.getFormatting())
diff --git a/src/main/java/io/github/lucaargolo/seasons/utils/ModConfig.java b/src/main/java/io/github/lucaargolo/seasons/utils/ModConfig.java
index dac0107..45aac65 100644
--- a/src/main/java/io/github/lucaargolo/seasons/utils/ModConfig.java
+++ b/src/main/java/io/github/lucaargolo/seasons/utils/ModConfig.java
@@ -22,6 +22,8 @@ private static class SeasonLength {
}
+ private Season startingSeason = Season.SPRING;
+
private SeasonLength seasonLength = new SeasonLength();
private SeasonLock seasonLock = new SeasonLock();
@@ -151,6 +153,20 @@ public Season getLockedSeason() {
return seasonLock.lockedSeason;
}
+ public Season getStartingSeason() {
+ return startingSeason;
+ }
+
+ public boolean isValidStartingSeason() {
+ return switch(startingSeason) {
+ case SPRING -> true;
+ case SUMMER -> true;
+ case FALL -> true;
+ case WINTER -> true;
+ default -> false;
+ };
+ }
+
public boolean isValidInDimension(RegistryKey dimension) {
return dimensionAllowlist.contains(dimension.getValue().toString());
}