WHAT YOU WILL MAKE
Verifiable and rereadable rule configuration platform
Configuration is not to insert any random text into a digital grid. First check the type, scope and world and replace qualified candidate configurations with new rules; when reading fails, the last valid configuration continues to work. Front-end backpack: Gradle project in Lesson 31, commands in Lesson 32, and exception handling in Lesson 28. Continue to use Java21 and ordinary Paper1.21.11. The new project name is BlockAcademyL36.
Before you start: It is recommended to complete Lessons 28, 31–35 first
01 / 15
Before starting: first recognize the running position
The first 30 lessons lay the foundation for ordinary Java programs. At this stage, the code is connected to the Paper server: the Minecraft client is responsible for entering the world, the server loads the plug-in, and the events and commands hand over player actions to your methods.
This book uses JDK 21 and Paper 1.21.11, and the client version is also consistent. The latest version on the download page may have changed, select it by version number. This book uses normal plugin.yml and the Bukkit API; no claims are made to support Folia at this stage.
The short examples in the picture frame are partial fragments. Read the location description of this section first, and do not save each as a plug-in. The file name will be clearly given for the complete file; when the same file is divided into multiple segments, they will be connected by number, and the package name, import and outer class will only appear once.
- Prediction: Write success conditions and expected messages first.
- Run: After successful construction, install and restart on the dedicated local test server.
- Verification: Ordinary players, management identities and consoles take corresponding paths respectively.
02 / 15
There is no need to rebuild the oven for one more piece of bread
The server owner just wants to change the reward bread from 3 pieces to 4 pieces, and A Fang is already preparing to reprogram the entire plug-in. Profiles are like oven knobs: when the rules allow for adjustments, turn the knob to change the setting, don’t tear down the house just yet.
The configuration template provides default values, the disk file saves the actual settings, and the memory saves the currently effective rules. When reloading, the candidate configurations are first completely read and verified, and only the old rules in memory are replaced after all are qualified.
- Template default valueStarting point for first installation
- Disk configurationFiles edited by the server owner
- Memory rulesThe settings that are currently in effect
Put into src/main/resources/config.yml · See the instructions in this section for the location
welcome-message: "Welcome to Block Academy!"
starter-bread: 3
ore-rush:
duration-seconds: 60
world: "world"- starter-bread controls the number of test rewards; 0 means closed.
- ore-rush.world specifies a world that allows bread testing.
- duration-seconds is for viewing first, and then the same idea is used to control the game duration.
03 / 15
Template files and files in use
The config.yml in the JAR is like a blank recipe book; the one in the plugins folder is the book that the server owner is filling in. saveDefaultConfig only copies the default file when the target file does not exist, and will not erase the teacher's changes every day when the computer is turned on.
- resources/config.ymlDefault template distributed with JAR
- Plug-in data directoryCurrent server configuration
- saveConfigWrite back, not reread
Prepare the default file when enabled. See the description in this section for the location.
saveDefaultConfig();Two different positions · See the description in this section for the position
src/main/resources/config.yml
plugins/BlockAcademyL36/config.yml| File | Responsibilities | How to take effect after the change |
|---|---|---|
| plugin.yml | Plug-in identity and command | Rebuild and restart |
| config.yml in JAR | Default value for first installation | Rebuilding affects new installations |
| Data directory config.yml | Current server rules | This lesson uses /rules reload |
04 / 15
Check the type first and then check the size
The number 3, the text "3" and the decimal 3.5 look like relatives, but they cannot all live in the integer grid. Just clamping the negative numbers to 0 still doesn't explain the input "many pieces of bread". We check the type first and then the agreed scope.
- TypeInteger is not equal to alphanumeric
- RangeBread 0 to 64
- worldThe name corresponds to the loaded world
Three values that look similar · See the instructions in this section for their positions
starter-bread: 3 # Integer Qualified
starter-bread: "3" # Text Reject
starter-bread: 3.5 # Decimal RejectRead nested keys · See the description in this section for the location
Object value = config.get("ore-rush.duration-seconds");| Configuration items | Agreement in this lesson |
|---|---|
| welcome-message | Non-empty text, up to 80 Java characters |
| starter-bread | Integer 0 to 64 |
| ore-rush.duration-seconds | Integer 1 to 3600, unit second |
| ore-rush.world | Non-empty name and world loaded |
05 / 15
New rules: Change shift only after all the new rules are passed
If the bread is first changed to 64, and then it is discovered that the world name is misspelled, the server is in a half-new and half-old rule. We first read a candidate configuration, check all fields and worlds, and finally replace the rules in use with only one assignment.
Stop and think about it: the reward amount is legal, but the active seconds are written in text. Can I apply the reward first and then report the seconds error?
- Read candidate filesKeep the old rules unchanged
- Complete verificationBoth the field and the world are qualified
- One shift changerules point to new records
Before entering the next step along the arrow, the conditions of the previous step must be met; the rejected branch returns early.
Check the world before applying · See the description in this section for the location
RuleSettings next = RuleSettings.read(candidate);
if (getServer().getWorld(next.world()) == null)
throw new IllegalArgumentException("World not loading");
rules = next;Don’t change half of it first · Intentional mistakes, predict first and then fix them
// Wrong idea: update rewards first, then check the world
// If the second step fails, the first step has taken effect.06 / 15
Concentrate the inspection in the rule class
Create a new RuleSettings.java in the same package. Record is Java's data record type: data is listed in parentheses, and Java automatically provides welcome(), bread() and other reading methods. The following method returns a record that has passed the field check; whether the world exists is checked by the plugin.
- Read-only recordsString and int storage rules
- text verificationNon-empty text and length
- integer checkInteger types and ranges
Complete RuleSettings.java · Complete file
package de.teacherma.lesson36;
import org.bukkit.configuration.file.FileConfiguration;
public record RuleSettings(String welcome, int bread,
int seconds, String world) {
public static RuleSettings read(FileConfiguration c) {
String welcome = text(c, "welcome-message");
int bread = integer(c, "starter-bread", 0, 64);
int seconds = integer(c, "ore-rush.duration-seconds", 1, 3600);
String world = text(c, "ore-rush.world");
return new RuleSettings(welcome, bread, seconds, world);
}
private static String text(FileConfiguration c, String key) {
Object value = c.get(key);
if (!(value instanceof String s) || s.isBlank() || s.length() > 80)
throw new IllegalArgumentException(key + " Yes. 1 to 80 non-empty literal of word");
return s;
}
private static int integer(FileConfiguration c, String key, int min, int max) {
Object value = c.get(key);
if (!(value instanceof Integer n) || n < min || n > max)
throw new IllegalArgumentException(key + " Requires integer " + min + ".." + max);
return n;
}
}07 / 15
Item list of configuration station
The project follows the build.gradle.kts and Java21 settings of Lesson 31, and the rootProject.name of settings.gradle.kts is changed to BlockAcademyL36. The two Java files are placed in the lesson36 package; the two yml files are placed in the resources directory.
- Two Java filesRule class and main class
- plugin.ymlIdentity, Commands and Permissions
- config.ymlDefault settings for first time installation
Complete plugin.yml · Complete file
name: BlockAcademyL36
version: 1.0.0
main: de.teacherma.lesson36.RuleDeskPlugin
api-version: '1.21.11'
author: TeacherMa
website: https://www.teacher-ma.de
commands:
rules:
description: View and test classroom configurations
usage: /rules <show|reload|snack>
permissions:
blockacademy.rules.admin:
default: opJava file directory · See the description in this section for the location
src/main/java/de/teacherma/lesson36/
RuleSettings.java
RuleDeskPlugin.java08 / 15
Configure the upper half of the main class
Create a new RuleDeskPlugin.java, put this page first, and then the next page. candidate.load will hand over reading or YAML format issues to catch; do not mistake the empty configuration after failure as a valid new rule.
- Prepare for configurationCopy only if it does not exist
- Load candidatesCapture read, write or parse failure
- Initialization commandOnly continue if the startup configuration is qualified.
Main class part 1 · This paragraph 1/2, sequential connection
package de.teacherma.lesson36;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
public final class RuleDeskPlugin extends JavaPlugin {
private RuleSettings rules;
@Override
public void onEnable() {
saveDefaultConfig();
if (!loadRules()) {
getServer().getPluginManager().disablePlugin(this);
return;
}
Objects.requireNonNull(getCommand("rules")).setExecutor(this);Main Class Part 1·This paragraph 2/2, sequential connection
}
private boolean loadRules() {
try {
var candidate = new YamlConfiguration();
candidate.load(new File(getDataFolder(), "config.yml"));
RuleSettings next = RuleSettings.read(candidate);
if (getServer().getWorld(next.world()) == null)
throw new IllegalArgumentException("World not loading: " + next.world());
rules = next;
return true;
} catch (IOException | InvalidConfigurationException
| IllegalArgumentException ex) {
getLogger().log(Level.WARNING, "Configuration not applied, please check config.yml", ex);
return false;
}09 / 15
Configure the lower half of the main class
Continue to the previous page, retaining all curly braces. show displays the current rules; reload tries to change the rules; snack tests the actual rewards. The unknown parameters are only explained and cannot be easily distributed to a cart of bread.
- showSee the current effective rules
- reloadRenewal rules only after passing the test
- snackDemonstration rewards by configuration
Main Class Part 2·This paragraph 1/2, sequential connection
}
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!sender.hasPermission("blockacademy.rules.admin")) {
sender.sendMessage("This classroom test requires teacher authorization");
return true;
}
if (args.length != 1) {
sender.sendMessage("Usage: /rules show or reload or snack");
return true;
}
switch (args[0]) {
case "reload" -> sender.sendMessage(loadRules()
? "The new configuration has taken effect" : "Configuration failed, continue to use the last valid rule");
case "show" -> {
sender.sendMessage(rules.welcome());
sender.sendMessage("Bread.=" + rules.bread() + ", Duration=" + rules.seconds()
+ "seconds, world=" + rules.world());
}
case "snack" -> {
if (!(sender instanceof Player p)) {
sender.sendMessage("Please test the bread in the game");
return true;
}Main Class Part 2·This paragraph 2/2, sequential connection
if (!p.getWorld().getName().equals(rules.world())) {
p.sendMessage("Please go to the world specified by the configuration first");
return true;
}
if (rules.bread() == 0) { p.sendMessage("Bread reward has been closed"); return true; }
var left = p.getInventory().addItem(new ItemStack(Material.BREAD, rules.bread()));
left.values().forEach(item -> p.getWorld().dropItemNaturally(p.getLocation(), item));
p.sendMessage(left.isEmpty() ? "The bread has been put into the backpack" : "The remaining bread is at your feet");
}
default -> sender.sendMessage("Parameters can only be show, reload, snack");
}
return true;
}
}10 / 15
Zero reward and full backpack are also considered normal situations
0 pieces of bread means that the teacher has turned off the reward, and there is no need to create a pile of items with a quantity of 0. Even if the backpack is full, it is not the end of the world for the program: addItem will return the part that cannot be loaded. Practice example: Place the remaining items at the player's feet and prompt them accordingly.
- Quantity 0Rewards disabled; do not create an item stack with quantity 0
- Backpack can accommodateNormal packet entry
- The backpack cannot fitOnly process the remaining part
Process the closed state first. See the description in this section for the location.
if (rules.bread() == 0) {
p.sendMessage("Bread reward has been closed");
return true;
}Only put the remaining part on the ground. See the instructions in this section for the location.
left.values().forEach(item ->
p.getWorld().dropItemNaturally(p.getLocation(), item));11 / 15
Change three into four with your own hands
Build and install first, enter the game run/rules show, and confirm that the bread is 3. Only modify the starter-bread of the data directory to 4, save the file, and then execute /rules reload. Finally, use snack to verify that there is actually one more piece, instead of just trusting the success prompt on the screen.
- Change disk fileBread 3 is changed to 4
- Reread explicitlyExecute rules reload
- Verify actual rewardShow and snack are checked together
Build and practice commands · See the description in this section for the location
.\gradlew.bat clean build
/rules show
/rules reload
/rules snack| Modification | Expected |
|---|---|
| 3 changed to 4 | The new configuration takes effect, and the reward is 4 yuan |
| Set to 0 | Close the prompt and do not send items |
| Set to 65 or negative number | Reread failed, old rules remain unchanged |
| Change to "4" | Reject literal numbers |
| The world name is spelled incorrectly | Deny, old world rules still valid |
12 / 15
Let the error indicate which line to fix
It is not enough to report an error saying "configuration failed". The rule check writes the key name into the exception information; the main class records the exception and stack, and the chat bar only gives brief feedback. Find the specific configuration key first, and then let someone with administrative rights decide when to reread it.
- Format errorCheck for indented colon quotes
- Rule errorException points to specific key
- Loading errorKeep logs and old rules
Keep abnormal clues · See the description in this section for the location
getLogger().log(Level.WARNING, "Configuration not applied, please check config.yml", ex);| Phenomenon | Check first |
|---|---|
| YAML parsing error | Indentation, colon, quotation marks |
| Requires integer 0..64 | Whether the field is an integer and within the range |
| World not loading | World name and server actual world |
| Command not available after initial startup | Whether the enable log shows configuration failure |
| The value remains the same after reload | Whether a failure prompt appears and whether the wrong file is corrected |
13 / 15
Give the seconds to the next event
Lesson 35: Write 60 in the code. Now when starting the activity, copy the verified seconds to the local variable. If I change the configuration after the current game starts, should it affect the next game, or should I change the current game immediately? Write the rules clearly first, then write the code.
- Snapshot of this fieldOpening copy verified seconds
- Change configuration on the wayThis site is still in accordance with the original contract
- Next gameAdopt new value that takes effect later
Take a snapshot at the beginning. See the description in this section for the location.
int thisRoundSeconds = rules.seconds();
// Use this value to initialize the countdown of this field
// Don't do it in every tick Re-read the file.Expectations for both games · Locations are explained in this section
At the beginning of the first game: 60 seconds
Modify during operation to: 90 seconds
The first game is maintained 60 seconds, next field use 90 seconds- The rules plug-in for this lesson only displays the duration and will not automatically start the competition.
- Lesson 38 will create a competition plug-in that actually uses duration configuration.
- Reading small configurations only occurs at startup and when the administrator explicitly rereads; do not read the disk every time you mine.
14 / 15
Expansion challenges and own acceptance records
Leave the source code, actual running results and a problem record. Explain it in your own words first, and then run the verification; where the results are different from the predictions, they are often the most worthy of discussion.
- Wooden PickaxeChange welcome message
- Iron PickaxeProve that bad configurations do not overwrite old rules
- Diamond PickaxeDesign new fields and boundaries
- Wooden Pickaxe: Configure your own welcome message for show, and explain the difference between templates and effective files.
- Iron Pickaxe: Complete 0, 64, 65, negative numbers, alphanumeric and wrong world name tests, the old rules remain unchanged after failure.
- Diamond Pickaxe: Design the three-level bread reward configuration, first write the field and range description, and then add a read check.
- Acceptance requirements: Test missing keys, error types, negative numbers and boundary values; after reloading a bad configuration, prove that the last valid setting is still in effect.
| I can complete it independently | Mastered | Practice again |
|---|---|---|
| Can find the config.yml being used | □ | □ |
| Type range and world will be checked | □ | □ |
| Can explain when candidate rules are replaced | □ | □ |
15 / 15
Leave the evidence for the next adventure
Divide "can compile", "number rules pass" and "actual performance of the game is correct" into three columns. Scenarios that are not running are written to be tested; when bugs are discovered, trigger conditions, expected, actual, minimum repairs, and retest results are recorded.
| Category | The operation you selected | Evidence left behind |
|---|---|---|
| Normal | A successful path for this lesson | Version and actual output |
| Border | The moment before the end, the maximum value or the full backpack | Compare item by item with contract |
| Reject | Wrong parameters, no permissions or irrelevant events | The feedback is clear and the status has not been changed by mistake. |
- Explain why each arrow in this lesson points from here to there; the parallel cards have no sequential meaning.
- First change a rule, then modify the description and expectations at the same time, and retest with the same input.
TAKE IT WITH YOU
Take the lesson offline
The complete lesson, code, diagrams and exercises, in your preferred language.
