player-homes¶
Per-player homes: /sethome remembers where you stand, /home teleports you back. Where
join-leave-messages shows the smallest wiring, this example shows
the lifecycle: a component that loads state from disk before anything uses
it, saves it on shutdown, and aborts startup when the data is unreadable. The full source
lives in
examples/bukkit/player-homes.
The plugin main¶
HomesPlugin is just the registry hook: the commands bind themselves through
@CommandHandler, so there is nothing to attach in onWeftEnable, see
listeners and commands.
package org.weftkit.examples.homes;
import org.weftkit.wiring.Registry;
import org.weftkit.wiring.bukkit.WeftPlugin;
import org.weftkit.wiring.registry.ComponentRegistry;
@Registry
public final class HomesPlugin extends WeftPlugin {
@Override
protected ComponentRegistry registry() {
return WeftWiring.INSTANCE;
}
}
The loader¶
HomesFile owns homes.yml. As a Loader it reads the file in load, returning false on a
corrupt file so the plugin never runs with silently empty homes, and saves it back in
unload. Its @Provides getter exposes the parsed YamlConfiguration as an injectable
value, captured right after load. The class stays package-private, only the provided value
leaves the package.
package org.weftkit.examples.homes.storage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import org.weftkit.wiring.Loader;
import org.weftkit.wiring.Provides;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;
@Wired
@Singleton
final class HomesFile implements Loader {
private final JavaPlugin plugin;
private final File file;
private YamlConfiguration homes;
HomesFile(JavaPlugin plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "homes.yml");
}
@Override
public boolean load() {
YamlConfiguration loaded = new YamlConfiguration();
try {
if (file.exists()) loaded.load(file);
} catch (IOException | InvalidConfigurationException ex) {
plugin.getLogger().log(Level.SEVERE, "homes.yml is unreadable, refusing to start", ex);
return false;
}
homes = loaded;
return true;
}
@Override
public void unload() {
try {
homes.save(file);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Could not save homes.yml", ex);
}
}
@Provides
YamlConfiguration homes() {
return homes;
}
}
The store¶
HomeStore injects that provided configuration and turns it into a typed set and find API.
Because it depends on the provided value, it is guaranteed to load after HomesFile, no
ordering annotations needed.
package org.weftkit.examples.homes.storage;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;
@Wired
@Singleton
public final class HomeStore {
private final YamlConfiguration homes;
public HomeStore(YamlConfiguration homes) {
this.homes = homes;
}
public void set(Player player) {
homes.set(player.getUniqueId().toString(), player.getLocation());
}
public Location find(Player player) {
return homes.getLocation(player.getUniqueId().toString());
}
}
The commands¶
SetHomeCommand
and
HomeCommand
are CommandExecutor components injecting the store, each @CommandHandlered to its
plugin.yml command so the bundled command module binds it during enable.
package org.weftkit.examples.homes.commands;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.weftkit.examples.homes.storage.HomeStore;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;
import org.weftkit.wiring.bukkit.commands.CommandHandler;
@Wired
@Singleton
@CommandHandler("sethome")
public final class SetHomeCommand implements CommandExecutor {
private final HomeStore homes;
public SetHomeCommand(HomeStore homes) {
this.homes = homes;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players have homes.");
return true;
}
homes.set(player);
player.sendMessage(ChatColor.YELLOW + "Home set.");
return true;
}
}
Homes are held in memory while the server runs and written once on disable. A real plugin might save on every change instead; the lifecycle hooks stay the same.
Run it¶
Build the example as described in the overview, drop the jar into a server's
plugins/ folder, run /sethome, wander off, and /home brings you back.
To see the aborted startup, put garbage into plugins/PlayerHomes/homes.yml and restart: the
plugin logs the parse failure and disables itself instead of coming up empty.