mirror of https://github.com/Minestom/Minestom.git
feat: add weather to instances (#2032)
* feat: instance weather system * chore: weather documentation * chore: remove unused weather fields * feat: linear weather interpolation * chore: register weather command --------- Co-authored-by: DeidaraMC <DeidaraMC> Co-authored-by: mworzala <mattheworzala@gmail.com>
This commit is contained in:
parent
9cfffc6ee4
commit
32f96683ee
|
@ -75,6 +75,7 @@ public class Main {
|
|||
commandManager.register(new SetEntityType());
|
||||
commandManager.register(new RelightCommand());
|
||||
commandManager.register(new KillCommand());
|
||||
commandManager.register(new WeatherCommand());
|
||||
|
||||
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED)));
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ public class PlayerInit {
|
|||
});
|
||||
instanceContainer.setChunkSupplier(LightingChunk::new);
|
||||
instanceContainer.setTimeRate(0);
|
||||
instanceContainer.setTime(18000);
|
||||
instanceContainer.setTime(6000);
|
||||
|
||||
// var i2 = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, null, NamespaceID.from("minestom:demo"));
|
||||
// instanceManager.registerInstance(i2);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package net.minestom.demo.commands;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.Weather;
|
||||
|
||||
public class WeatherCommand extends Command {
|
||||
public WeatherCommand() {
|
||||
super("weather");
|
||||
|
||||
var isRaining = ArgumentType.Boolean("isRaining").setDefaultValue(false);
|
||||
var rainLevel = ArgumentType.Float("rainLevel").setDefaultValue(0.0f);
|
||||
var thunderLevel = ArgumentType.Float("thunderLevel").setDefaultValue(0.0f);
|
||||
var transitionTicks = ArgumentType.Integer("transition").setDefaultValue(0);
|
||||
addSyntax(this::handleWeather, isRaining, rainLevel, thunderLevel, transitionTicks);
|
||||
}
|
||||
|
||||
private void handleWeather(CommandSender source, CommandContext context) {
|
||||
Player player = (Player) source;
|
||||
boolean isRaining = context.get("isRaining");
|
||||
float rainLevel = context.get("rainLevel");
|
||||
float thunderLevel = context.get("thunderLevel");
|
||||
int transitionTicks = context.get("transition");
|
||||
player.getInstance().setWeather(new Weather(isRaining, rainLevel, thunderLevel), transitionTicks);
|
||||
}
|
||||
}
|
|
@ -891,6 +891,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||
if (this instanceof Player player) {
|
||||
instance.getWorldBorder().init(player);
|
||||
player.sendPacket(instance.createTimePacket());
|
||||
player.sendPackets(instance.getWeather().createWeatherPackets());
|
||||
}
|
||||
instance.getEntityTracker().register(this, spawnPosition, trackingTarget, trackingUpdate);
|
||||
spawn();
|
||||
|
|
|
@ -83,6 +83,11 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
private Duration timeUpdate = Duration.of(1, TimeUnit.SECOND);
|
||||
private long lastTimeUpdate;
|
||||
|
||||
// Weather of the instance
|
||||
private Weather targetWeather = new Weather(false, 0, 0);
|
||||
private Weather currentWeather = new Weather(false, 0, 0);
|
||||
private int remainingWeatherTransitionTicks;
|
||||
|
||||
// Field for tick events
|
||||
private long lastTickAge = System.currentTimeMillis();
|
||||
|
||||
|
@ -668,6 +673,13 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
}
|
||||
|
||||
}
|
||||
// Weather
|
||||
if (remainingWeatherTransitionTicks > 0) {
|
||||
Weather previousWeather = currentWeather;
|
||||
currentWeather = transitionWeather(remainingWeatherTransitionTicks);
|
||||
sendWeatherPackets(previousWeather);
|
||||
remainingWeatherTransitionTicks--;
|
||||
}
|
||||
// Tick event
|
||||
{
|
||||
// Process tick events
|
||||
|
@ -678,6 +690,45 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
this.worldBorder.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current weather on this instance
|
||||
*
|
||||
* @return the current weather
|
||||
*/
|
||||
public @NotNull Weather getWeather() {
|
||||
return currentWeather;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the weather on this instance, transitions over time
|
||||
*
|
||||
* @param weather the new weather
|
||||
* @param transitionTicks the ticks to transition to new weather
|
||||
*/
|
||||
public void setWeather(@NotNull Weather weather, int transitionTicks) {
|
||||
Check.stateCondition(transitionTicks < 1, "Transition ticks cannot be lower than 1");
|
||||
targetWeather = weather;
|
||||
remainingWeatherTransitionTicks = transitionTicks;
|
||||
}
|
||||
|
||||
private void sendWeatherPackets(@NotNull Weather previousWeather) {
|
||||
if (currentWeather.isRaining() != previousWeather.isRaining()) sendGroupedPacket(currentWeather.createIsRainingPacket());
|
||||
if (currentWeather.rainLevel() != previousWeather.rainLevel()) sendGroupedPacket(currentWeather.createRainLevelPacket());
|
||||
if (currentWeather.thunderLevel() != previousWeather.thunderLevel()) sendGroupedPacket(currentWeather.createThunderLevelPacket());
|
||||
}
|
||||
|
||||
private @NotNull Weather transitionWeather(int remainingTicks) {
|
||||
Weather target = targetWeather;
|
||||
Weather current = currentWeather;
|
||||
if (remainingTicks <= 1) {
|
||||
return new Weather(target.isRaining(), target.isRaining() ? target.rainLevel() : 0,
|
||||
target.isRaining() ? target.thunderLevel() : 0);
|
||||
}
|
||||
float rainLevel = current.rainLevel() + (target.rainLevel() - current.rainLevel()) * (1 / (float)remainingTicks);
|
||||
float thunderLevel = current.thunderLevel() + (target.thunderLevel() - current.thunderLevel()) * (1 / (float)remainingTicks);
|
||||
return new Weather(rainLevel > 0, rainLevel, thunderLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TagHandler tagHandler() {
|
||||
return tagHandler;
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import it.unimi.dsi.fastutil.floats.FloatUnaryOperator;
|
||||
import net.minestom.server.network.packet.server.SendablePacket;
|
||||
import net.minestom.server.network.packet.server.play.ChangeGameStatePacket;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the possible weather properties of an instance
|
||||
*
|
||||
* @param isRaining true if the instance is raining, otherwise false
|
||||
* @param rainLevel a percentage between 0 and 1
|
||||
* used to change how heavy the rain is
|
||||
* higher values darken the sky and increase rain opacity
|
||||
* @param thunderLevel a percentage between 0 and 1
|
||||
* used to change how heavy the thunder is
|
||||
* higher values further darken the sky
|
||||
*/
|
||||
public record Weather(boolean isRaining, float rainLevel, float thunderLevel) {
|
||||
/**
|
||||
* @throws IllegalArgumentException if {@code rainLevel} is not between 0 and 1
|
||||
* @throws IllegalArgumentException if {@code thunderLevel} is not between 0 and 1
|
||||
*/
|
||||
public Weather {
|
||||
Check.argCondition(!MathUtils.isBetween(rainLevel, 0, 1), "Rain level should be between 0 and 1");
|
||||
Check.argCondition(!MathUtils.isBetween(thunderLevel, 0, 1), "Thunder level should be between 0 and 1");
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Weather withRain(boolean isRaining) {
|
||||
return new Weather(isRaining, rainLevel, thunderLevel);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Weather withRainLevel(float rainLevel) {
|
||||
return new Weather(isRaining, rainLevel, thunderLevel);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Weather withRainLevel(@NotNull FloatUnaryOperator operator) {
|
||||
return withRainLevel(operator.apply(rainLevel));
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Weather withThunderLevel(float thunderLevel) {
|
||||
return new Weather(isRaining, rainLevel, thunderLevel);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Weather withThunderLevel(@NotNull FloatUnaryOperator operator) {
|
||||
return withRainLevel(operator.apply(thunderLevel));
|
||||
}
|
||||
|
||||
public ChangeGameStatePacket createIsRainingPacket() {
|
||||
return new ChangeGameStatePacket(isRaining ? ChangeGameStatePacket.Reason.BEGIN_RAINING : ChangeGameStatePacket.Reason.END_RAINING, 0);
|
||||
}
|
||||
|
||||
public ChangeGameStatePacket createRainLevelPacket() {
|
||||
return new ChangeGameStatePacket(ChangeGameStatePacket.Reason.RAIN_LEVEL_CHANGE, rainLevel);
|
||||
}
|
||||
|
||||
public ChangeGameStatePacket createThunderLevelPacket() {
|
||||
return new ChangeGameStatePacket(ChangeGameStatePacket.Reason.THUNDER_LEVEL_CHANGE, thunderLevel);
|
||||
}
|
||||
|
||||
public @NotNull Collection<SendablePacket> createWeatherPackets() {
|
||||
return List.of(createIsRainingPacket(), createRainLevelPacket(), createThunderLevelPacket());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.network.packet.server.play.ChangeGameStatePacket;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
@EnvTest
|
||||
public class WeatherTest {
|
||||
@Test
|
||||
public void weatherTest(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
|
||||
// Defaults
|
||||
Weather weather = instance.getWeather();
|
||||
assertFalse(weather.isRaining());
|
||||
assertEquals(0, weather.rainLevel());
|
||||
assertEquals(0, weather.thunderLevel());
|
||||
|
||||
instance.setWeather(new Weather(true, 1, 0.5f), 1);
|
||||
instance.tick(0);
|
||||
|
||||
// Weather sent on instance join
|
||||
var connection = env.createConnection();
|
||||
var tracker = connection.trackIncoming(ChangeGameStatePacket.class);
|
||||
connection.connect(instance, new Pos(0, 0, 0)).join();
|
||||
tracker.assertCount(4);
|
||||
List<ChangeGameStatePacket> packets = tracker.collect();
|
||||
var state = packets.get(0);
|
||||
assertEquals(ChangeGameStatePacket.Reason.BEGIN_RAINING, state.reason());
|
||||
|
||||
state = packets.get(1);
|
||||
assertEquals(ChangeGameStatePacket.Reason.RAIN_LEVEL_CHANGE, state.reason());
|
||||
assertEquals(1, state.value());
|
||||
|
||||
state = packets.get(2);
|
||||
assertEquals(ChangeGameStatePacket.Reason.THUNDER_LEVEL_CHANGE, state.reason());
|
||||
assertEquals(0.5f, state.value());
|
||||
|
||||
// Weather change while inside instance
|
||||
var tracker2 = connection.trackIncoming(ChangeGameStatePacket.class);
|
||||
instance.setWeather(new Weather(false, 0, 0), 2);
|
||||
instance.tick(0);
|
||||
state = tracker2.collect().get(0);
|
||||
assertEquals(ChangeGameStatePacket.Reason.RAIN_LEVEL_CHANGE, state.reason());
|
||||
assertEquals(0.5f, state.value());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue