Arbeite an Gameplay-Systemen auf einem etablierten Minecraft-Netzwerk.
Team aktuell vollständig besetzt!Registrierte Spieler
Serverbetrieb
Custom Items & Systeme
Tausende Spielstunden
Ein Auktionssystem, das jede Woche drei alte Event-Items freigibt. Gebote funktionieren serverübergreifend in Echtzeit. Realisiert mit Redis Pub/Sub und Redis-Locks.
Serverübergreifendes Mailsystem für Spieler. Nachrichten können auch an offline Spieler gesendet werden und kosten Ingame-Währung.
Ein Spezialitem für Builder, das viele Bauaktionen vereinfacht und beschleunigt. Technisch umgesetzt mit der FastAsyncWorldEdit API.
Neue Spezialitems, Tools und Spielmechaniken für CityBuild.
Weiterentwicklung bestehender Systeme wie Auktionshaus, Jobs oder Events.
Umsetzung von Vorschlägen aus der Community oder dem Team - von kleinen QoL-Features bis zu neuen Spielmechaniken.
@CommandDefinition(
label = "info",
usage = "(Player)",
rank = PixelRank.SUPPORTER
)
public class InfoCommand extends PixelCommand<ExamplePlugin> {
public InfoCommand(ExamplePlugin plugin) {
super(plugin);
}
@Override
public void onPlayerCommand(BukkitPixelPlayer player, String label, String[] args) {
if (args.length != 1) {
player.sendMessage(getPlayerUsage());
return;
}
String targetName = args[0];
PlayerHandler playerHandler = BukkitCoreLibrary.getPlayerHandler();
BukkitPixelPlayer target = playerHandler.getPlayer(targetName);
// online player → direct
if (target != null) {
sendInformation(player, target);
return;
}
// offline player → async load
DataLoader loader = DataLoaderBuilder.create(DataLoaderBuilderType.SINGLE_PLAYER)
.name("InfoCommand")
.addRequireds(BasePlayerData.class, RankPlayerData.class)
.build();
BukkitExecutor.runAsync(getPlugin(), () -> {
try {
BukkitPixelPlayer loaded = playerHandler.getPlayer(targetName, loader);
BukkitExecutor.runSync(getPlugin(), () -> {
if (!player.isOnline()) return;
if (loaded == null) {
player.sendMessage(PLAYER_IS_NOT_REGISTERED);
return;
}
sendInformation(player, loaded);
});
} catch (Exception e) {
player.sendMessage(DATA_LOAD_FAILED);
}
});
}
private void sendInformation(BukkitPixelPlayer player, BukkitPixelPlayer target) {
BasePlayerData data = target.getData(BasePlayerData.class);
player.sendMessage(target.getColoredName() + " first join: " + data.getFirstJoin());
}
}
// basic custom item with persistent data
NamespacedKey usesKey = new NamespacedKey(plugin, "egg-uses");
ItemStack easterEgg = ItemBuilder.material(Material.EGG)
.name("<rainbow>Easteregg</rainbow>")
.lore(
"<gray>A rare drop of the easter event</gray>",
"<dark_gray>Limited uses</dark_gray>"
)
.glowing()
.unbreakable(true)
.persistentData(usesKey, PersistentDataType.INTEGER, 5)
.maximumAmount(1)
.build();
// enchantments (books vs normal items)
ItemStack protectionBook = ItemBuilder.enchantedBook(
Enchantment.PROTECTION
).build();
ItemStack customEnchantItem = ItemBuilder.material(Material.DIAMOND)
.enchantment(
Map.of(
CoreEnchantment.SOUL_BOUND, 1,
Enchantment.FORTUNE, 10
)
)
.build();
// player head (online player)
ItemStack playerHead = ItemBuilder.skull(player)
.name(player.getName() + "'s head")
.build();
// async skull loading (by any player name)
String playerName = "_Monty_";
BukkitExecutor.runAsync(plugin, () -> {
try {
SkullUtil.SkullData skullData = SkullUtil.getSkullData(playerName);
BukkitExecutor.runSync(plugin, () -> {
ItemStack customHead = ItemBuilder.material(Material.PLAYER_HEAD)
.anvilName(playerName + "'s head")
.skullTexture(skullData)
.build();
player.sendMessage("Loaded custom head for " + playerName);
});
} catch (Exception ignored) {}
});
public class ExampleInventory extends PixelPrivateInventory<ExamplePlugin> {
public ExampleInventory(ExamplePlugin plugin, Component title, InventoryType type, int size) {
super(plugin, title, type, size);
}
@Override
public void setItems() {
setItem(0, createItem(Material.DIAMOND, "<aqua>Reward</aqua>"));
setItem(1, createItem(Material.BARRIER, "<red>Close</red>"));
}
@Override
public void onClickInventory(InventoryClickEvent event) {
event.setCancelled(true);
ItemStack item = event.getCurrentItem();
if (item == null || item.getType() == Material.AIR) return;
BukkitPixelPlayer player = getPlayer();
if (event.getSlot() == 0) {
player.addOrDropItem(
ItemBuilder.material(Material.DIAMOND)
.name("<green>Reward claimed</green>")
.build()
);
player.playGenericClickSound();
}
if (event.getSlot() == 1) {
player.closeInventory();
}
}
private ItemStack createItem(Material material, String name) {
return ItemBuilder.material(material)
.name(name)
.glowing()
.build();
}
}
// opening
inventoryHandler.openInventory(
plugin,
player,
ExampleInventory.class,
"Example GUI",
9
);
public class SendBroadcastPacket extends PixelJedisPacket {
@Serial
private static final long serialVersionUID = 0L;
private final String gson;
public SendBroadcastPacket(Component component) {
this.gson = ComponentUtil.toGson(component);
}
public String getGson() {
return gson;
}
}
public class SendBroadcastListener extends PixelJedisPacketListener {
@JedisPacketListenerDefinition
public void onBroadcast(SendBroadcastPacket packet) {
Component component = ComponentUtil.fromGson(packet.getGson());
PlayerHandler playerHandler = BukkitCoreLibrary.getPlayerHandler();
for (BukkitPixelPlayer player : playerHandler.getOnlinePlayers()) {
player.sendMessage(component);
}
}
}
// packet sending
redisHandler.sendServerPacket(
PixelServerCategory.CITY_BUILD,
new SendBroadcastPacket(
ComponentBuilder.create("Server-wide message")
.hover("Just a test message!")
.build()
)
);
Du arbeitest weitgehend selbstständig an Features, triffst eigene technische Entscheidungen und bringst deine Ideen aktiv ein.
Die Entwicklung erfolgt über Feature Branches und Merge Requests mit Code Reviews.
Unsere Core API ist ausführlich in einer MkDocs-Wiki dokumentiert. Weitere Systeme werden
schrittweise ergänzt.
Mit unserer Docker-basierten Entwicklungs-umgebung (MPCKER) kannst du Änderungen lokal umsetzen und testen - vollständig unabhängig vom Livebetrieb.
Absprachen, Fragen und Feedback erfolgen unkompliziert und auf kurzen Wegen über Discord.
Unser Entwicklerteam ist momentan vollständig besetzt. Daher nehmen wir derzeit keine neuen Bewerbungen an. Sobald wieder eine Stelle frei wird, kannst du dich hier bewerben.