Skip to content

Commit fd8d026

Browse files
authored
Merge pull request #3459 from Multiverse/feat/bonus-chest-force-spawn
Add support for creating world with bonus chest and force spawn
2 parents a7d0672 + 35133ee commit fd8d026

5 files changed

Lines changed: 189 additions & 9 deletions

File tree

src/main/java/org/mvplugins/multiverse/core/commands/CreateCommand.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import co.aikar.commands.annotation.Subcommand;
1212
import co.aikar.commands.annotation.Syntax;
1313
import com.dumptruckman.minecraft.util.Logging;
14+
import io.vavr.control.Try;
1415
import jakarta.inject.Inject;
1516
import org.bukkit.World;
1617
import org.bukkit.WorldType;
@@ -24,9 +25,11 @@
2425
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
2526
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
2627
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
28+
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
2729
import org.mvplugins.multiverse.core.locale.MVCorei18n;
2830
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
2931
import org.mvplugins.multiverse.core.utils.StringFormatter;
32+
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
3033
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
3134
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
3235
import org.mvplugins.multiverse.core.world.WorldManager;
@@ -52,8 +55,9 @@ class CreateCommand extends CoreCommand {
5255
@Subcommand("create")
5356
@CommandPermission("multiverse.core.create")
5457
@CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME)
55-
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
56-
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
58+
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> " +
59+
"--adjust-spawn --no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> " +
60+
"--biome <biome> --properties <prop1=value1,prop2=value2,...>]")
5761
@Description("{@@mv-core.create.description}")
5862
void onCreateCommand(
5963
MVCommandIssuer issuer,
@@ -67,8 +71,9 @@ void onCreateCommand(
6771
World.Environment environment,
6872

6973
@Optional
70-
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
71-
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
74+
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn " +
75+
"--no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> --biome <biome> " +
76+
"--properties <prop1=value1,prop2=value2,...>]")
7277
@Description("{@@mv-core.create.flags.description}")
7378
String[] flagArray) {
7479
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
@@ -79,14 +84,16 @@ void onCreateCommand(
7984

8085
worldManager.createWorld(CreateWorldOptions.worldName(worldName)
8186
.biome(parsedFlags.flagValue(flags.biome, ""))
87+
.bonusChest(parsedFlags.hasFlag(flags.bonusChest))
8288
.environment(environment)
83-
.seed(parsedFlags.flagValue(flags.seed))
84-
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))
85-
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
89+
.forcedSpawnPosition(parsedFlags.flagValue(flags.forceSpawnPosition))
8690
.generator(parsedFlags.flagValue(flags.generator, ""))
8791
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
8892
.generateStructures(!parsedFlags.hasFlag(flags.noStructures))
89-
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties))))
93+
.seed(parsedFlags.flagValue(flags.seed))
94+
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
95+
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties)))
96+
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL)))
9097
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
9198
.onFailure(failure -> messageFailure(issuer, failure));
9299
}
@@ -185,6 +192,15 @@ private Flags(
185192
private final CommandValueFlag<String> properties = flag(CommandValueFlag.builder("--properties", String.class)
186193
.addAlias("-p")
187194
.build());
195+
196+
private final CommandFlag bonusChest = flag(CommandFlag.builder("--generate-bonus-chest")
197+
.addAlias("-c")
198+
.build());
199+
200+
private final CommandValueFlag<EntityPosition> forceSpawnPosition = flag(CommandValueFlag.builder("--force-spawn-position", EntityPosition.class)
201+
.addAlias("-f")
202+
.context(input -> Try.of(() -> EntityPosition.fromString(input)).getOrElseThrow(MVInvalidCommandArgument::causeBy))
203+
.build());
188204
}
189205

190206
@Service

src/main/java/org/mvplugins/multiverse/core/exceptions/command/MVInvalidCommandArgument.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.mvplugins.multiverse.core.exceptions.command;
22

33
import co.aikar.commands.InvalidCommandArgument;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.mvplugins.multiverse.core.locale.message.LocalizableMessage;
46
import org.mvplugins.multiverse.core.locale.message.LocalizedMessage;
57
import org.mvplugins.multiverse.core.locale.message.Message;
68

@@ -9,6 +11,18 @@
911
*/
1012
public class MVInvalidCommandArgument extends InvalidCommandArgument {
1113

14+
@ApiStatus.AvailableSince("5.7")
15+
public static MVInvalidCommandArgument causeBy(Throwable throwable) {
16+
return causeBy(throwable, true);
17+
}
18+
19+
@ApiStatus.AvailableSince("5.7")
20+
public static MVInvalidCommandArgument causeBy(Throwable throwable, boolean showSyntax) {
21+
return (throwable instanceof LocalizableMessage localizableMessage)
22+
? of(localizableMessage.getLocalizableMessage(), showSyntax)
23+
: new MVInvalidCommandArgument(throwable.getLocalizedMessage(), showSyntax);
24+
}
25+
1226
public static MVInvalidCommandArgument of(Message message) {
1327
return of(message, true);
1428
}
@@ -19,6 +33,10 @@ public static MVInvalidCommandArgument of(Message message, boolean showSyntax) {
1933
: new MVInvalidCommandArgument(message, showSyntax);
2034
}
2135

36+
private MVInvalidCommandArgument(String message, boolean showSyntax) {
37+
super(message, showSyntax);
38+
}
39+
2240
private MVInvalidCommandArgument(Message message, boolean showSyntax) {
2341
super(message.formatted(), showSyntax);
2442
}

src/main/java/org/mvplugins/multiverse/core/utils/compatibility/WorldCreatorCompatibility.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org.mvplugins.multiverse.core.utils.compatibility;
22

3+
import com.dumptruckman.minecraft.util.Logging;
34
import io.vavr.control.Try;
45
import org.bukkit.NamespacedKey;
56
import org.bukkit.WorldCreator;
67
import org.jetbrains.annotations.ApiStatus;
78
import org.jetbrains.annotations.NotNull;
89
import org.mvplugins.multiverse.core.utils.ReflectHelper;
10+
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
911
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
1012

1113
import java.lang.reflect.Method;
@@ -19,10 +21,17 @@
1921
@ApiStatus.AvailableSince("5.7")
2022
public final class WorldCreatorCompatibility {
2123

24+
private static final Try<Class<?>> POSITION_CLASS;
25+
private static final Try<Method> FORCED_SPAWN_POSITION_METHOD;
2226
private static final Try<Method> OF_KEY_METHOD;
2327
private static final Try<Method> OF_NAME_AND_KEY_METHOD;
28+
private static final Try<Method> BONUS_CHEST_METHOD;
2429

2530
static {
31+
POSITION_CLASS = ReflectHelper.tryGetClass("io.papermc.paper.math.Position");
32+
FORCED_SPAWN_POSITION_METHOD = POSITION_CLASS.flatMap(positionClass ->
33+
ReflectHelper.tryGetMethod(WorldCreator.class, "forcedSpawnPosition", positionClass, float.class, float.class));
34+
BONUS_CHEST_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "bonusChest", boolean.class);
2635
OF_KEY_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "ofKey", NamespacedKey.class);
2736
OF_NAME_AND_KEY_METHOD = ReflectHelper.tryGetMethod(WorldCreator.class, "ofNameAndKey", String.class, NamespacedKey.class);
2837
}
@@ -105,6 +114,81 @@ private static boolean canPassIntoNameAndKey(@NotNull NamespacedKey worldKey, @N
105114
&& worldName.toLowerCase(Locale.ROOT).equals(worldKey.getKey()));
106115
}
107116

117+
/**
118+
* Checks if the server supports configuring a forced spawn position via the WorldCreator API.
119+
* <br />
120+
* The force spawn position API is generally only available on PaperMC 26.1+
121+
*
122+
* @return Whether force spawn position API is supported on the current server version.
123+
*
124+
* @since 5.7
125+
*/
126+
@ApiStatus.AvailableSince("5.7")
127+
public static boolean supportsForcedSpawnPosition() {
128+
return FORCED_SPAWN_POSITION_METHOD.isSuccess();
129+
}
130+
131+
/**
132+
* Tries to set the forced spawn position if the server implements the API. This call will do nothing if server
133+
* software does not implement the required APIs.
134+
*
135+
* @param worldCreator Target creator instance to set on.
136+
* @param position The position to set as the forced spawn point for the world.
137+
*
138+
* @since 5.7
139+
*/
140+
@ApiStatus.AvailableSince("5.7")
141+
public static void setForcedSpawnPosition(WorldCreator worldCreator, EntityPosition position) {
142+
if (!supportsForcedSpawnPosition()) {
143+
Logging.fine("Server does not support forced spawn position configuration via WorldCreator API.");
144+
return;
145+
}
146+
ReflectHelper.tryInvokeMethod(
147+
worldCreator,
148+
FORCED_SPAWN_POSITION_METHOD.get(),
149+
io.papermc.paper.math.Position.fine(
150+
position.getVector().getX().getRawValue(),
151+
position.getVector().getY().getRawValue(),
152+
position.getVector().getZ().getRawValue()
153+
),
154+
(float) position.getDirection().getYaw().getRawValue(),
155+
(float) position.getDirection().getPitch().getRawValue()
156+
).onFailure(ex ->
157+
Logging.warning("Failed to set forced spawn position on WorldCreator: %s", ex.getMessage()));
158+
}
159+
160+
/**
161+
* Checks if the server supports configuring bonus chest generation via the WorldCreator API.
162+
* <br />
163+
* The bonus chest API is generally only available on PaperMC 1.21.5+
164+
*
165+
* @return Whether bonus chest API is supported on the current server version.
166+
*
167+
* @since 5.7
168+
*/
169+
@ApiStatus.AvailableSince("5.7")
170+
public static boolean supportsBonusChest() {
171+
return BONUS_CHEST_METHOD.isSuccess();
172+
}
173+
174+
/**
175+
* Tries to set bonus chest if the server implements the API. This call will do nothing if server software does not
176+
* implement the required APIs.
177+
*
178+
* @param worldCreator Target creator instance to set on.
179+
* @param generateBonusChest Whether to generate a bonus chest at the world spawn point.
180+
*
181+
* @since 5.7
182+
*/
183+
@ApiStatus.AvailableSince("5.7")
184+
public static void setBonusChest(WorldCreator worldCreator, boolean generateBonusChest) {
185+
if (!supportsBonusChest()) {
186+
Logging.fine("Server does not support bonus chest generation via WorldCreator API.");
187+
return;
188+
}
189+
worldCreator.bonusChest(generateBonusChest);
190+
}
191+
108192
private WorldCreatorCompatibility() {
109193
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
110194
}

src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ private Attempt<LoadedMultiverseWorld, CreateFailureReason> doCreateWorld(
283283
.generatorSettings(options.generatorSettings())
284284
.seed(options.seed())
285285
.type(options.worldType());
286-
286+
WorldCreatorCompatibility.setBonusChest(worldCreator, options.bonusChest());
287+
options.forcedSpawnPosition().peek(position -> WorldCreatorCompatibility.setForcedSpawnPosition(worldCreator, position));
287288
return addBiomeProviderToCreator(worldCreator, keyOrName.usableName(), options.biome())
288289
.mapAttempt(creator -> addGeneratorToCreator(creator, generatorString))
289290
.mapAttempt(this::createBukkitWorld)

src/main/java/org/mvplugins/multiverse/core/world/options/CreateWorldOptions.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import co.aikar.commands.ACFUtil;
44
import io.vavr.control.Either;
5+
import io.vavr.control.Option;
56
import org.bukkit.NamespacedKey;
67
import org.bukkit.World;
78
import org.bukkit.WorldType;
89
import org.jetbrains.annotations.ApiStatus;
910
import org.jetbrains.annotations.NotNull;
1011
import org.jetbrains.annotations.Nullable;
1112
import org.jetbrains.annotations.UnmodifiableView;
13+
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
1214
import org.mvplugins.multiverse.core.world.key.WorldKeyOrName;
1315

1416
import java.util.Collections;
@@ -61,7 +63,9 @@ public final class CreateWorldOptions {
6163

6264
private final Either<String, WorldKeyOrName> keyOrName;
6365
private String biome = "";
66+
private boolean bonusChest = false;
6467
private World.Environment environment = World.Environment.NORMAL;
68+
private EntityPosition forcedSpawnPosition = null;
6569
private boolean generateStructures = true;
6670
private String generator = null;
6771
private String generatorSettings = "";
@@ -125,6 +129,32 @@ public final class CreateWorldOptions {
125129
return biome;
126130
}
127131

132+
/**
133+
* Sets whether bonus chest should generate at spawn upon world creation.
134+
* <br />
135+
* This feature only works on PaperMC 1.21.5+
136+
*
137+
* @param bonusChest Whether bonus chest should generate at spawn upon world creation.
138+
* @return This {@link CreateWorldOptions} instance.
139+
*/
140+
@ApiStatus.AvailableSince("5.7")
141+
public @NotNull CreateWorldOptions bonusChest(boolean bonusChest) {
142+
this.bonusChest = bonusChest;
143+
return this;
144+
}
145+
146+
/**
147+
* Gets whether bonus chest should generate at spawn upon world creation.
148+
* <br />
149+
* This feature only works on PaperMC 1.21.5+
150+
*
151+
* @return true if bonus chest should generate, else false.
152+
*/
153+
@ApiStatus.AvailableSince("5.7")
154+
public boolean bonusChest() {
155+
return bonusChest;
156+
}
157+
128158
/**
129159
* Sets the environment of the world to create.
130160
*
@@ -145,6 +175,37 @@ public final class CreateWorldOptions {
145175
return environment;
146176
}
147177

178+
/**
179+
* Sets the forced spawn position of the world to apply. This may be null, in which case the spawn position will
180+
* be determined by the default generator. Setting spawn position and {@link #useSpawnAdjust(boolean)} to false
181+
* will improve world creation speed on PaperMC as chunks will not be loaded to search for spawn point.
182+
* <br />
183+
* This feature only works on PaperMC 26.1+
184+
*
185+
* @param forcedSpawnPosition The forced spawn position of the world to create.
186+
* @return This {@link CreateWorldOptions} instance.
187+
*
188+
* @since 5.7
189+
*/
190+
@ApiStatus.AvailableSince("5.7")
191+
public @NotNull CreateWorldOptions forcedSpawnPosition(@Nullable EntityPosition forcedSpawnPosition) {
192+
this.forcedSpawnPosition = forcedSpawnPosition;
193+
return this;
194+
}
195+
196+
/**
197+
* Sets the forced spawn position of the world to apply. This may be null, in which case the spawn position will
198+
* be determined by the default generator.
199+
* <br />
200+
* This feature only works on PaperMC 26.1+
201+
*
202+
* @return The force spawn position to apply if available.
203+
*/
204+
@ApiStatus.AvailableSince("5.7")
205+
public @NotNull Option<EntityPosition> forcedSpawnPosition() {
206+
return Option.of(this.forcedSpawnPosition);
207+
}
208+
148209
/**
149210
* Sets whether structures such as NPC villages should be generated.
150211
*

0 commit comments

Comments
 (0)