Docs · For mod authors · API 1.6.0Doku · Für Mod-Autoren · API 1.6.0

Add-on APIAdd-on-API

Extend Propwash FPV from your own mod without changing Propwash: new parts of every category, presets, a creative tab, buttons in the workbench, configurator tabs, Flight School scenes and a share-code system.

Propwash FPV aus dem eigenen Mod erweitern, ohne Propwash zu ändern: neue Teile jeder Kategorie, Presets, ein Kreativ-Tab, Knöpfe in der Werkbank, Konfigurator-Tabs, Flugschul-Szenen und ein Sharecode-System.

Propwash 0.5.2 · Minecraft 26.3 · Fabric & NeoForge · API 1.6.0 · protocolProtokoll 16

What add-ons can doWas Add-ons können

An add-on is a normal Fabric or NeoForge mod that depends on Propwash and talks to it only through the package de.lardtom.propwash.api. It never changes Propwash itself, and built-in parts, saves and flight behaviour stay exactly as they are. Propwash: Just More Parts is built this way.

Ein Add-on ist ein normaler Fabric- oder NeoForge-Mod, der von Propwash abhängt und nur über das Paket de.lardtom.propwash.api mit ihm spricht. Propwash selbst ändert es nie; eingebaute Teile, Speicherstände und Flugverhalten bleiben genau, wie sie sind. Propwash: Just More Parts ist so gebaut.

Possible with API 1.6.0Möglich mit API 1.6.0Not (yet) possible(Noch) nicht möglich
Frames, stacks, motors, props, batteries, video units and accessories, in Java or JSONRahmen, Stacks, Motoren, Props, Akkus, Videomodule und Zubehör, in Java oder JSONother motor counts than four (hexa, octo)andere Motorzahlen als vier (Hexa, Okto)
own arm geometry, camera position, centre of mass, ducts, drag, battery rules per frameeigene Arm-Geometrie, Kameraposition, Schwerpunkt, Ducts, Luftwiderstand, Akku-Regeln je Rahmennew video systems or goggles (only analog and digital)neue Videosysteme oder Brillen (nur analog und digital)
new motor and stack mounts, prop mounts, cell chemistries up to 12Sneue Motor- und Stack-Halterungen, Prop-Aufnahmen, Zellchemien bis 12Sparts from world data packs or /reloadTeile aus Welt-Datenpaketen oder per /reload
items, tooltips, workbench, analysis, flight model, rendering and saving come for freeItems, Tooltips, Werkbank, Analyse, Flugmodell, Rendering und Speichern gibt es geschenktnew paint slots, accessory mounts beyond top, bottom and front, placeable elements in the OSD editorneue Lack-Plätze, Zubehör-Halterungen außer oben, unten und vorne, platzierbare Elemente im OSD-Editor
presets and an own creative tab right after the Propwash tabPresets und ein eigener Kreativ-Tab direkt hinter dem Propwash-Tabown item classes for parts (Propwash provides the part items)eigene Item-Klassen für Teile (Propwash stellt die Teil-Items)
reading builds, creating drone items, setting tune and paint, a server-checked importBaupläne lesen, Drohnen-Items erzeugen, Tuning und Lack setzen, ein vom Server geprüfter Importchanging or removing built-in partseingebaute Teile ändern oder entfernen
workbench buttons, child screens, configurator tabs, Flight School scenes, events, chest lootWerkbank-Knöpfe, Kind-Fenster, Konfigurator-Tabs, Flugschul-Szenen, Events, Truhen-Beute 

Project setupProjekt aufsetzen

Gradle

Propwash publishes three Maven artifacts (group de.lardtom, each with sources and Javadoc): propwash-common-26.3 with the whole API in Mojang names, and the loader jars propwash-fabric-26.3 and propwash-neoforge-26.3. Minecraft 26.3 is not obfuscated, so no remapping is involved. From the Modrinth Maven:

Propwash veröffentlicht drei Maven-Artefakte (Gruppe de.lardtom, je mit Quellen und Javadoc): propwash-common-26.3 mit der ganzen API in Mojang-Namen und die Loader-Jars propwash-fabric-26.3 und propwash-neoforge-26.3. Minecraft 26.3 ist nicht obfuskiert, es wird also nichts umbenannt. Über das Modrinth-Maven:

GradlecompileOnly in common, implementation in the loader modulecompileOnly in common, implementation im Loader-Modul
repositories {
    exclusiveContent {
        forRepository { maven { url = 'https://api.modrinth.com/maven' } }
        filter { includeGroup 'maven.modrinth' }
    }
}

dependencies {
    compileOnly 'maven.modrinth:propwash-fpv:0.5.2+neoforge'
    implementation 'maven.modrinth:propwash-fpv:0.5.2+fabric'
    implementation 'maven.modrinth:propwash-fpv:0.5.2+neoforge'
}

The variants carry the attribute io.github.mcgradleconventions.loader (common, fabric, neoforge) like the MultiLoader template, so an add-on built from that template picks the right one.

Die Varianten tragen das Attribut io.github.mcgradleconventions.loader (common, fabric, neoforge) wie im MultiLoader-Template; ein Add-on aus diesem Template bekommt damit von selbst die passende.

The Modrinth Maven above only resolves once Propwash is through Modrinth’s review (see the download section of the landing page). Until then, build Propwash from its source repository (github.com/LardTom/propwash-fpv) or take the jar from CurseForge once the version you need is listed there, and depend on it as a local file or a mavenLocal() publish instead:

Das Modrinth-Maven oben löst erst auf, sobald Propwash die Prüfung bei Modrinth hinter sich hat (siehe den Download-Abschnitt der Startseite). Bis dahin Propwash aus dem Quellcode-Repository bauen (github.com/LardTom/propwash-fpv) oder das Jar von CurseForge nehmen, sobald die gebrauchte Version dort steht, und stattdessen als lokale Datei oder über mavenLocal() einbinden:

GradleFallback while Propwash is not on Modrinth yetÜbergangslösung, solange Propwash noch nicht auf Modrinth steht
repositories {
    mavenLocal() // only needed for the mavenLocal() dependencies below
}

dependencies {
    // Jar downloaded from CurseForge, placed in libs/
    compileOnly files('libs/propwash-common-26.3-0.5.2.jar')
    implementation files('libs/propwash-fabric-26.3-0.5.2.jar')

    // or: built from source with ./gradlew publishToMavenLocal
    // compileOnly 'de.lardtom:propwash-common-26.3:0.5.2'
    // implementation 'de.lardtom:propwash-fabric-26.3:0.5.2'
}

Dependency in the loader metadataAbhängigkeit in den Loader-Metadaten

Propwash has to run on client and server, and so does the add-on: the part lists are compared when a player connects.

Propwash muss auf Client und Server laufen, das Add-on ebenso: Beim Verbinden werden die Teile-Listen verglichen.

JSONfabric.mod.json
"depends": {
  "minecraft": "~26.3",
  "propwash": ">=0.5.2"
}
TOMLneoforge.mods.toml
[[dependencies.examplemod]]
modId = "propwash"
type = "required"
versionRange = "[0.5.2,)"
ordering = "AFTER"
side = "BOTH"

Name, version and homepage for the join overviewName, Version und Homepage für die Beitritts-Übersicht

If a player's add-ons don't match the server's, Propwash shows an overview with one card per add-on: name, mod id, the version on the server and on the player's side, what to do (“Install Your Add-on 1.2.3”) and a link to the add-on's page (see When add-ons don't match). These details come from the add-on's loader metadata, which the server sends along when a player connects (Propwash add-ons only, never the whole mod list):

Passen die Add-ons eines Spielers nicht zu denen des Servers, zeigt Propwash eine Übersicht mit einer Karte je Add-on: Name, Mod-Id, Version auf dem Server und beim Spieler, was zu tun ist („Installiere Dein Add-on 1.2.3“) und ein Link zur Seite des Add-ons (siehe Wenn Add-ons nicht passen). Diese Angaben stammen aus den Loader-Metadaten des Add-ons, die der Server beim Verbinden mitschickt (nur Propwash-Add-ons, nie die ganze Mod-Liste):

ShownAngabefabric.mod.jsonneoforge.mods.toml ([[mods]])
Name in the overviewName in der ÜbersichtnamedisplayName
Versionversionversion
Link (only https:// or http://)Link (nur https:// oder http://)contact.homepage, else contact.sourcescontact.homepage, sonst contact.sourcesdisplayURL, else modUrldisplayURL, sonst modUrl
Recognized as a Propwash add-onErkannt als Propwash-Add-onpropwash under depends, recommends or suggestspropwash unter depends, recommends oder suggests[[dependencies.<modid>]] with modId = "propwash"[[dependencies.<modid>]] mit modId = "propwash"
JSONfabric.mod.json
"name": "Propwash: My Add-on",
"version": "1.2.3",
"contact": { "homepage": "https://modrinth.com/mod/my-addon" }
TOMLneoforge.mods.toml
[[mods]]
modId = "examplemod"
version = "1.2.3"
displayName = "Propwash: My Add-on"
displayURL = "https://modrinth.com/mod/my-addon"

Use your mod id as the namespace of your parts: Propwash assigns parts to an add-on by their namespace. Control characters, § formatting and links with other schemes are dropped; names may have up to 96 characters, versions up to 64. Without name/displayName or a homepage, the card shows only the mod id and no link.

Die Mod-Id als Namensraum der Teile verwenden: Propwash ordnet Teile über ihren Namensraum dem Add-on zu. Steuerzeichen, §-Formatierung und Links mit anderem Schema verwirft Propwash; Namen höchstens 96 Zeichen, Versionen 64. Ohne name/displayName oder Homepage zeigt die Karte nur die Mod-Id und keinen Link.

Checking the API versionAPI-Version prüfen

The API has its own version (PropwashApi.API_VERSION, SemVer), independent of the mod version: Propwash 0.5.2 ships 1.6.0. When your mod starts, require the major version and the smallest minor version whose features you need, and test for newer ones before using them:

Die API hat eine eigene Version (PropwashApi.API_VERSION, SemVer), unabhängig von der Mod-Version: Propwash 0.5.2 bringt 1.6.0. Beim Start die Hauptversion und die kleinste Nebenversion verlangen, deren Funktionen das Add-on braucht, und neuere vor dem Benutzen abfragen:

Java
PropwashApi.requireVersion("examplemod", "1.1");
if (PropwashApi.isCompatible("1.4")) {
    PropwashApi.loot().inject(dungeon, ownTable);
}

requireVersion throws an IllegalStateException with a clear message when Propwash is too old or has a different major version.

requireVersion wirft eine IllegalStateException mit klarer Meldung, wenn Propwash zu alt ist oder eine andere Hauptversion hat.

Entry pointsEinstiegspunkte

PhaseWhenWannAllowedErlaubt
OpenOffenFabric ModInitializer#onInitialize, NeoForge @Mod constructor (both sides)Fabric ModInitializer#onInitialize, NeoForge-@Mod-Konstruktor (beide Seiten)registering parts, chemistries, items, presets, the creative tab, loot and event listenersTeile, Zellchemien, Items, Presets, Kreativ-Tab, Beute und Event-Listener anmelden
Client initClient-InitFabric ClientModInitializer, NeoForge @Mod(dist = Dist.CLIENT)PropwashClientApi.register…: workbench, configurator tabs, Flight SchoolPropwashClientApi.register…: Werkbank, Konfigurator-Tabs, Flugschule
FrozenEingefrorenafter all initialisation (event REGISTRY_FROZEN)nach allen Initialisierungen (Event REGISTRY_FROZEN)read only; register throws IllegalStateExceptionnur lesen; register wirft IllegalStateException
  • Mod order does not matter: every call of the open phase works before, during and after Propwash’s own initialisation.Die Reihenfolge der Mods ist egal: Jeder Aufruf der offenen Phase funktioniert vor, während und nach Propwashs eigener Initialisierung.
  • Always register parts in the common init, never only on the client, or the server disconnects players.Teile immer in der gemeinsamen Init anmelden, nie nur auf dem Client, sonst trennt der Server die Spieler.
  • On NeoForge the constructors run on a worker thread: do not touch anything that needs a running world or client there.Unter NeoForge laufen die Konstruktoren auf einem Worker-Thread: dort nichts anfassen, was eine laufende Welt oder einen Client braucht.
Java
public final class ExampleMod {
    public static final String MOD_ID = "examplemod";

    public static void init() {
        PropwashApi.requireVersion(MOD_ID, "1.6");
        ExampleParts.register();
        PropwashApi.parts().loadBundled(MOD_ID);
        PropwashApi.partItems().registerItems(MOD_ID);
        PropwashApi.builds().registerPreset(DronePreset.of(id("lr8s"), ExamplePresets.longRange8s()));
        PropwashApi.extensionTab(MOD_ID).icon(id("deadcat_5"))
                .order(TabSection.PRESETS, TabSection.FRAMES, TabSection.MOTORS, TabSection.PROPS);
        PropwashEvents.BUILD_IMPORTED.register((player, request, result) -> ExampleStats.count(result));
    }

    public static void initClient() {
        PropwashClientApi.registerWorkbenchExtension(new ShareCodeButton());
        PropwashClientApi.registerConfigTab(ShareCodeTab::new);
        PropwashClientApi.registerFlightSchoolScene(FrameShapesScene::create);
    }

    public static Identifier id(String path) {
        return Identifier.fromNamespaceAndPath(MOD_ID, path);
    }
}

PartsBauteile

Every category has a definition record in api.part with a builder. Build definitions (…Definition, AirframeGeometry, FrameAero) only through their builders or with… methods; their constructors gain fields in minor versions. Small value records such as Vec3Mm, BatteryFitRule, PackSize or CellChemistry may be created with new. Every record checks its values and throws IllegalArgumentException naming the id.

Jede Kategorie hat einen Definitions-Record in api.part mit Builder. Definitionen (…Definition, AirframeGeometry, FrameAero) nur über Builder oder with…-Methoden bauen; ihre Konstruktoren bekommen in Nebenversionen neue Felder. Kleine Wertrecords wie Vec3Mm, BatteryFitRule, PackSize oder CellChemistry dürfen mit new entstehen. Jeder Record prüft seine Werte und wirft IllegalArgumentException mit der Id.

Coordinates are millimetres with the origin in the middle of the frame at the height of the stack plate: +X right, +Y up, −Z forward (the nose). Arms are always listed front left, front right, rear left, rear right.

Koordinaten sind Millimeter mit dem Ursprung in der Rahmenmitte auf Höhe der Stack-Auflage: +X rechts, +Y oben, −Z vorn (die Nase). Arme stehen immer in der Reihenfolge vorne links, vorne rechts, hinten links, hinten rechts.

In Java

Java
PropwashApi.parts().register(MotorDefinition.builder(id("m2806_1300"))
        .mass(52.0).stator("2806.5").kv(1300).mount(Mounts.MOTOR_M28)
        .maxCurrent(48.0).resistance(0.062).cells(6, 8)
        .thermal(MotorThermal.limits(135.0, 155.0))
        .build());

PropwashApi.parts().register(PropDefinition.builder(id("p13_7_2b_carbon"))
        .mass(28.0).size(330, 7.0, 2).coefficients(0.095, 0.042)
        .material(PropMaterial.CARBON)
        .build());

PropwashApi.parts().register(FrameDefinition.builder(id("deadcat_5"))
        .mass(118.0).role(FrameRole.FREESTYLE).shape(FrameShape.DEADCAT)
        .wheelbase(230).maxProp(130)
        .motorMount(Mounts.MOTOR_M22).stackMount(Mounts.STACK_S30, Mounts.STACK_S20)
        .accessoryMount(AccessorySlot.TOP, AccessorySlot.BOTTOM)
        .arms(List.of(MotorArm.of(-95, 0, -60, MotorArm.Spin.CW), MotorArm.of(95, 0, -60, MotorArm.Spin.CCW),
                MotorArm.of(-80, 0, 80, MotorArm.Spin.CCW), MotorArm.of(80, 0, 80, MotorArm.Spin.CW)))
        .battery(true, 30.0).electronics(12.0)
        .camera(new Vec3Mm(0.0, 14.0, -100.0)).cameraTilt(30.0)
        .aero(0.012, 0.9).impactTolerance(8.0)
        .batteryFit(new BatteryFitRule(4, 6, 260.0, 6, 0))
        .build());
  • Stator sizes come from the label (first two digits diameter, the rest height) or from statorSize(d, h). A new mount is just a new id; frames that fit name the same id in motorMount(…).Statormaße kommen aus der Bezeichnung (erste zwei Ziffern Durchmesser, Rest Höhe) oder aus statorSize(d, h). Eine neue Halterung ist einfach eine neue Id; passende Rahmen nennen dieselbe Id in motorMount(…).
  • Without arms, Propwash builds a true X from the wheelbase. With arms, mixer, lever arms, inertia, hull and hitbox follow from the geometry.Ohne arms rechnet Propwash einen True-X aus dem Radstand. Mit arms entstehen Mixer, Hebelarme, Trägheit, Hülle und Hitbox aus der Geometrie.
  • Every frame should set camera with a position: the lens usually sits 1–5 mm below the prop plane on X frames. Check the result with PropwashApi.builds().analyze(spec) (props in view) or cameraView(spec).Jeder Rahmen sollte camera mit Position setzen: Das Objektiv sitzt bei X-Rahmen meist 1–5 mm unter der Propebene. Prüfen mit PropwashApi.builds().analyze(spec) (Props im Bild) oder cameraView(spec).
  • Register cell chemistries (registerChemistry) before the batteries that use them.Zellchemien (registerChemistry) vor den Akkus anmelden, die sie benutzen.

As JSONAls JSON

The same parts can be data files in the add-on’s own resources, data/<namespace>/propwash_parts/<category>/<path>.json, loaded by PropwashApi.parts().loadBundled(modId). All fields, types and defaults are on the page Parts format (JSON). Tools can use Propwash’s own codec: parts().toJson(definition) and parts().fromJson(kind, id, json).

Dieselben Teile können Datendateien in den eigenen Ressourcen des Add-ons sein, data/<namensraum>/propwash_parts/<kategorie>/<pfad>.json, geladen mit PropwashApi.parts().loadBundled(modId). Alle Felder, Typen und Standards stehen auf der Seite Teile-Format (JSON). Werkzeuge können Propwashs eigenen Codec benutzen: parts().toJson(definition) und parts().fromJson(kind, id, json).

What Propwash checksWas Propwash prüft

Not buildable (problems): missing_part, motor_mount, prop_too_large, prop_too_small, prop_mount, stack_mount, battery_frame, accessory_mount. Warnings: motor_overvoltage, motor_undervoltage, stack_voltage, esc_undersized, battery_weak, battery_undersized. Texts: propwash.build.issue.<key>.

Nicht baubar (Probleme): missing_part, motor_mount, prop_too_large, prop_too_small, prop_mount, stack_mount, battery_frame, accessory_mount. Warnungen: motor_overvoltage, motor_undervoltage, stack_voltage, esc_undersized, battery_weak, battery_undersized. Texte: propwash.build.issue.<schlüssel>.

Items, assets, textsItems, Assets, Texte

Java
int count = PropwashApi.partItems().registerItems("examplemod");
PropwashApi.partItems().registerItem(id("xclass_13"), p -> p.rarity(Rarity.RARE));
  • The item has the same id as the part. Propwash provides the item class (batteries with charge), stack size, Shift tooltips, wear bar, recycling, workbench slots and the Flight School link.Das Item hat dieselbe Id wie das Teil. Propwash liefert die Item-Klasse (Akkus mit Ladestand), Stapelgröße, Shift-Tooltips, Verschleißbalken, Recycling, Werkbank-Slots und die Flugschul-Zuordnung.
  • Register individually configured parts before registerItems. Look-ups: item(id), stack(id), stack(id, charge) for batteries, partOf(stack).Einzeln eingestellte Teile vor registerItems anmelden. Nachschlagen: item(id), stack(id), stack(id, charge) für Akkus, partOf(stack).
  • Without own models Propwash draws every part procedurally from its definition. For the inventory icon the special renderer propwash:part is enough; category base models propwash:item/part_base/<category> give add-on parts the same size as built-in ones in hand, on the ground and in item frames.Ohne eigene Modelle zeichnet Propwash jedes Teil prozedural aus seiner Definition. Für das Inventar-Symbol reicht der Sonder-Renderer propwash:part; die Kategorie-Basismodelle propwash:item/part_base/<kategorie> geben Add-on-Teilen dieselbe Größe wie eingebauten in der Hand, am Boden und im Item-Rahmen.
JSONassets/examplemod/items/m2806_1300.json
{"model": {"type": "minecraft:special", "base": "propwash:item/part_base/motor", "model": {"type": "propwash:part"}}}
PurposeZweckLanguage keySprachschlüssel
Part name (required)Teilname (Pflicht)part.<ns>.<path>
Shift helpShift-Hilfe<ns>.help.<path>.summary, .what.1…8, .use.1…8
Short frame name (class, leaderboard)Rahmen-Kurzname (Klasse, Bestenliste)<ns>.frame_short.<path>
MountsHalterungen<ns>.mount.motor.<path>, .mount.stack.<path>, .mount.prop.<path>
Cell chemistryZellchemie<ns>.chemistry.<path>
Preset<ns>.preset.<path>
Name of the own creative tabName des eigenen Kreativ-TabsitemGroup.<ns>
Flight School sceneFlugschul-Szene<ns>.guide.scene.<name>.title, .description, .step.<step>.title/text

Recipes are normal vanilla recipes of the add-on; the intermediate items propwash:carbon_plate, copper_coil, circuit_board, lipo_cell and hd_chip are stable ingredients. The tags #propwash:parts and #propwash:parts/<category> can be extended with "replace": false. Own chest loot goes into any vanilla table with PropwashApi.loot().inject(target, addonTable), on both loaders.

Rezepte sind normale Vanilla-Rezepte des Add-ons; die Zwischenteile propwash:carbon_plate, copper_coil, circuit_board, lipo_cell und hd_chip sind stabile Zutaten. Die Tags #propwash:parts und #propwash:parts/<kategorie> lassen sich mit "replace": false ergänzen. Eigene Truhen-Beute hängt PropwashApi.loot().inject(ziel, addonTabelle) in jede Vanilla-Tabelle, auf beiden Loadern.

Presets and creative tabPresets und Kreativ-Tab

Java
DroneData longRange = new DroneData(
        BuildSpec.of(id("lr_8"), id("stack_30_8s"), id("m2806_1300"), Identifier.fromNamespaceAndPath("propwash", "p7_2b"),
                Identifier.fromNamespaceAndPath("propwash", "digital_hd"), id("b8s_4500_lion")),
        PaintSpec.DEFAULT.with("frame", 0x1B1D22), TuneSpec.EMPTY, Optional.of("LR 8S"));
PropwashApi.builds().registerPreset(DronePreset.of(id("lr8s"), longRange));

PropwashApi.extensionTab("examplemod")
        .icon(id("deadcat_5"))
        .order(TabSection.PRESETS, TabSection.FRAMES, TabSection.MOTORS, TabSection.PROPS)
        .add(() -> List.of(PropwashApi.builds().createDrone(longRange.withName("LR 8S Race"))));
  • Every add-on gets its own creative tab right after the Propwash tab, titled “Propwash: <name>” with the name from itemGroup.<ns>. Part items and listed presets of the namespace go there, not into the Propwash tab.Jedes Add-on bekommt einen eigenen Kreativ-Tab direkt hinter dem Propwash-Tab, Titel „Propwash: <Name>“ mit dem Namen aus itemGroup.<ns>. Teil-Items und gelistete Presets des Namensraums landen dort, nicht im Propwash-Tab.
  • order(…) sorts the sections; sections you do not name follow in the default order. An empty tab is hidden by Minecraft.order(…) sortiert die Abschnitte; nicht genannte folgen in der Standard-Reihenfolge. Einen leeren Tab blendet Minecraft aus.

Builds and importBaupläne und Import

TypeTypContentInhalt
BuildSpecids of frame, stack, motor (×4), prop (×4), video, battery and the accessory per slotIds von Rahmen, Stack, Motor (×4), Prop (×4), Video, Akku und Zubehör je Platz
PaintSpecRGB per paint slot: frame, tpu, motors, prop_fl … prop_rr, stack, battery, camera, antenna, accessoriesRGB je Lack-Platz: frame, tpu, motors, prop_fl … prop_rr, stack, battery, camera, antenna, accessories
TuneSpeckey → value like in the flight controller (at most 256 entries)Schlüssel → Wert wie im Flight-Controller (höchstens 256 Einträge)
DroneDataBuildSpec + PaintSpec + TuneSpec + name (up to 48 characters)BuildSpec + PaintSpec + TuneSpec + Name (bis 48 Zeichen)

PropwashApi.builds() reads drone items (read), creates them (createDrone, withTune, withPaint), checks and analyses builds (check, analyze with the same figures as the workbench, cameraView, defaultTune) and runs imports on the server (applyImport). Battery charge, wear, bindings and switch assignments are never part of a DroneData.

PropwashApi.builds() liest Drohnen-Items (read), erzeugt sie (createDrone, withTune, withPaint), prüft und analysiert Baupläne (check, analyze mit denselben Werten wie die Werkbank, cameraView, defaultTune) und führt Importe auf dem Server aus (applyImport). Akkustand, Verschleiß, Bindungen und Schalterbelegung gehören nie zu einer DroneData.

Import: the server decidesImport: Der Server entscheidet

Java
context.importIntoWorkbench(data, EnumSet.allOf(ImportLayer.class), TuneGroup.FLIGHT).thenAccept(this::show);
PropwashClientApi.sendImport(ImportRequest.tuneOnHeldDrone(data)).thenAccept(this::show);
PropwashClientApi.sendImport(ImportRequest.paintOnHeldDrone(data)).thenAccept(this::show);
PropwashClientApi.sendCreateDrone(data).thenAccept(this::show);

The client sends a request; the server checks size and rate (at most two requests per second and player), the target, unknown parts and the game mode, applies the layers in the order parts → paint → tune → name and answers with an ImportResult. In survival, parts come from the inventory and paint costs dyes, as in the game.

Der Client schickt eine Anfrage; der Server prüft Größe und Rate (höchstens zwei Anfragen pro Sekunde und Spieler), das Ziel, unbekannte Teile und den Spielmodus, wendet die Ebenen in der Reihenfolge Teile → Lack → Tuning → Name an und antwortet mit einem ImportResult. Im Überlebensmodus kommen Teile aus dem Inventar und Lack kostet Farbstoffe, wie im Spiel.

Statuses: APPLIED, UNCHANGED, UNKNOWN_PARTS, MISSING_PARTS, MISSING_DYES, NO_TARGET, NOT_ALLOWED, INVALID, BUSY; show them with Component.translatable(result.status().translationKey()).

Status: APPLIED, UNCHANGED, UNKNOWN_PARTS, MISSING_PARTS, MISSING_DYES, NO_TARGET, NOT_ALLOWED, INVALID, BUSY; anzeigen mit Component.translatable(result.status().translationKey()).

Share codes live in the add-on: Propwash provides the data, the check and the server-checked import. Never put the video channel (TuneGroup.VTX), switch assignments, battery charge or bindings into a code.

Sharecodes leben im Add-on: Propwash liefert Daten, Prüfung und den vom Server geprüften Import. Nie den Videokanal (TuneGroup.VTX), Schalterbelegung, Akkustand oder Bindungen in einen Code schreiben.

Client extensionsClient-Erweiterungen

Workbench buttonsWerkbank-Knöpfe

Java
public final class ShareCodeButton implements WorkbenchExtension {
    @Override
    public Identifier id() {
        return ExampleMod.id("share_code");
    }

    @Override
    public void init(WorkbenchContext context) {
        context.addButton(Component.translatable("examplemod.workbench.code"),
                Component.translatable("examplemod.workbench.code.tooltip"),
                () -> context.openChild(parent -> new ShareCodeScreen(parent, context)));
    }
}

init runs every time the workbench screen is built. Up to two buttons sit left of Paint, more go behind “⋯”; headerSpace(), short labels and 13 × 13 px icon buttons help with long texts. WorkbenchContext gives the slots, the finished drone (result()), missingParts(data) and the import into the workbench.

init läuft bei jedem Aufbau des Werkbank-Screens. Bis zu zwei Knöpfe stehen links neben Lackierung, weitere hinter „⋯“; headerSpace(), Kurzbeschriftungen und Symbol-Knöpfe mit 13 × 13 px helfen bei langen Texten. WorkbenchContext liefert die Slots, die fertige Drohne (result()), missingParts(data) und den Import in die Werkbank.

Configurator tabsKonfigurator-Tabs

PropwashClientApi.registerConfigTab(ShareCodeTab::new). A tab extends ui.client.config.ConfigTab: an id with namespace, its own order() from 1000, widgets in build(), drawing in extract(…), save() and discard(). openConfigTab(id, parent) opens the configurator right on it.

PropwashClientApi.registerConfigTab(ShareCodeTab::new). Ein Tab erweitert ui.client.config.ConfigTab: Id mit Namensraum, eigene order() ab 1000, Widgets in build(), Zeichnen in extract(…), save() und discard(). openConfigTab(id, parent) öffnet den Konfigurator direkt darauf.

Flight School scenesFlugschul-Szenen

Java
PropwashClientApi.registerFlightSchoolScene(FrameShapesScene::create);
PropwashClientApi.linkFlightSchoolParts("examplemod:frames", "examplemod", PartKind.FRAME);
PropwashClientApi.linkFlightSchoolParts(FlightSchoolScenes.REPAIR, "examplemod", PartKind.MOTOR);

Add-on scenes need an id in the add-on’s namespace and use the scene builder in guide.scene. Holding the Flight School key over an item opens its scenes; add-on batteries, video units, stacks and props get the matching Propwash scene automatically.

Add-on-Szenen brauchen eine Id im Namensraum des Add-ons und benutzen den Szenen-Baukasten in guide.scene. Die Flugschul-Taste über einem Item öffnet seine Szenen; Akkus, Videomodule, Stacks und Props von Add-ons bekommen die passende Propwash-Szene von selbst.

Theme

Every screen that hooks into Propwash uses the Propwash theme: ThemedScreen, ThemeDraw, ThemeText, Palette, Metrics, FlatButton, ScrollPanel, StepSlider and TextField. These classes are released for add-ons and only ever get additions.

Jedes Fenster, das sich in Propwash einklinkt, benutzt das Propwash-Theme: ThemedScreen, ThemeDraw, ThemeText, Palette, Metrics, FlatButton, ScrollPanel, StepSlider und TextField. Diese Klassen sind für Add-ons freigegeben und werden nur ergänzt.

Accessories, flight and HUDZubehör, Flug und HUD

Since API 1.6 add-ons can give accessories behaviour, react to the pilot, push the drone and fly it. Everything is optional; without an add-on using it Propwash flies exactly as before. Details, limits and threads: docs/design/addon-api-1.6.md in the source repository.

Seit API 1.6 können Add-ons Zubehör Verhalten geben, auf den Piloten reagieren, die Drohne anschieben und sie fliegen. Alles ist optional; ohne Add-on, das es benutzt, fliegt Propwash genau wie vorher. Einzelheiten, Grenzen und Threads: docs/design/addon-api-1.6.md im Quellcode-Repository.

Front mountHalterung vorne

Besides top and bottom there is front (AccessorySlot.FRONT): the accessory hangs under the nose and points forward. Every built-in frame except the Whoop has it; own frames add "front" to accessory_mounts and an anchor accessory_anchors.front.

Neben top und bottom gibt es front (AccessorySlot.FRONT): Das Zubehör hängt unter der Nase und zeigt nach vorn. Alle eingebauten Rahmen außer dem Whoop haben die Halterung; eigene Rahmen ergänzen "front" in accessory_mounts und einen Anbaupunkt accessory_anchors.front.

Accessory behaviour, inventory and actionsZubehör-Verhalten, Inventar und Aktionen

Java
PropwashApi.accessories().register(LAUNCHER, new AccessoryBehavior() {
    @Override public int slots() { return 4; }
    @Override public boolean accepts(int slot, ItemStack stack) { return stack.is(Items.ARROW); }
    @Override public void onAction(AccessoryAction action) {
        if (action.number() != 1 || !action.pressed()) return;
        int slot = action.inventory().firstFilled();
        if (slot < 0) return;
        ItemStack ammo = action.inventory().take(slot, 1);
        MountPose mount = action.mount();
        // spawn the projectile at mount.front(), owner action.player()
        action.physics().impulse(mount.forward().scale(-1.0), new Vec3(0.0, -0.03, -0.08));
    }
});

Players fill the inventory with the Accessories button in the drone’s service menu; the load stays with drone and item and adds weight in flight. Accessory action 1–4 are switch functions in the Modes tab and keys (numpad 9, 0, Enter and +; numpad 8 is turtle mode); every press and release is checked by the server (pilot, reset epoch, rate; a release only counts while the action is held) before onAction runs and ACCESSORY_ACTION fires. The Accessories window reads the drone’s current state before every access and writes back only the slots it changed itself, so writes through AccessoryInventory on the server and several open windows do not clash. It never takes shulker boxes (Item#canFitInsideContainerItems) or drone items with accessory contents of their own, even when accepts returns its default true.

Spieler füllen das Inventar mit dem Knopf Zubehör in der Wartung der Drohne; die Beladung bleibt an Drohne und Item und wiegt im Flug mit. Zubehör-Aktion 1–4 sind Schalterfunktionen im Tab Modi und Tasten (Ziffernblock 9, 0, Enter und +; Ziffernblock 8 ist der Turtle-Modus); jedes Drücken und Loslassen prüft der Server (Pilot, Reset-Epoche, Rate; ein Loslassen zählt nur, solange die Aktion gehalten ist), bevor onAction läuft und ACCESSORY_ACTION feuert. Das Fenster Zubehör liest vor jedem Zugriff den Stand der Drohne neu und schreibt nur die Plätze zurück, die es selbst geändert hat; Schreiben über AccessoryInventory am Server und mehrere offene Fenster vertragen sich damit. Shulkerkisten (Item#canFitInsideContainerItems) und Drohnen-Items mit eigenem Zubehör-Inhalt nimmt es nie an, auch wenn accepts die Vorgabe true liefert.

Impulses, pushes and forcesStöße, Schübe und Kräfte

PropwashApi.drones().physics(drone) gives impulse(J, framePoint) in N·s and push(F, frameRelative, framePoint, seconds) in N; Propwash sends them to the pilot’s flight model, the server return flight or a parked drone. Continuous forces (balloon, parachute) come from PropwashApi.flight().registerForces(id, profile -> provider) at 100 Hz on both sides. PropwashApi.drones() also finds drones near a point or a mob (with line of sight), aims with lead (aim) and scans for obstacles in six directions.

PropwashApi.drones().physics(drone) liefert impulse(J, rahmenpunkt) in N·s und push(F, rahmenrelativ, rahmenpunkt, sekunden) in N; Propwash schickt sie an das Flugmodell des Piloten, den Heimflug am Server oder eine abgestellte Drohne. Dauerhafte Kräfte (Ballon, Fallschirm) kommen von PropwashApi.flight().registerForces(id, profil -> anbieter) mit 100 Hz auf beiden Seiten. PropwashApi.drones() findet außerdem Drohnen nahe einem Punkt oder einem Mob (mit Sichtprüfung), zielt mit Vorhalt (aim) und sucht Hindernisse in sechs Richtungen.

Flight assistsFlughilfen

Java
PropwashApi.flight().registerAssist("examplemod:cine",
        profile -> profile.hasAccessory("examplemod:gimbal_cam") ? new CineAssist() : null);

A FlightAssist runs at 100 Hz inside the pilot’s flight model and gets a FlightState (true and GPS/barometer-estimated position and velocity, attitude, sticks, accessory actions, obstacle distances). Through AssistOutput it sets sticks and mode, an altitude or climb rate, a position or velocity with a speed limit (Propwash’s ALTHOLD/POSHOLD loops with their sensor errors), a yaw rate or heading, an OSD label and may start GPS Rescue. Assists rest during GPS Rescue. While an add-on assist outputs anything (sticks, mode, altitude, position, velocity, yaw), the race lap counts as an assisted lap: reason other with the assist’s id as subject (characters [a-z0-9_:./-], at most 48). If it holds altitude or position without the pilot having switched a hold mode, only the assist is named, not “altitude hold was on”. Forces from registerForces do not count.

Eine FlightAssist läuft mit 100 Hz im Flugmodell des Piloten und bekommt einen FlightState (wahre und per GPS/Barometer geschätzte Position und Geschwindigkeit, Lage, Knüppel, Zubehör-Aktionen, Hindernisabstände). Über AssistOutput setzt sie Knüppel und Modus, eine Höhe oder Steigrate, eine Position oder Geschwindigkeit mit Tempogrenze (die Regelkreise von ALTHOLD/POSHOLD mit ihren Sensorfehlern), eine Gierrate oder einen Kurs, eine OSD-Anzeige und darf GPS Rescue starten. Während GPS Rescue ruhen Flughilfen. Solange eine Flughilfe eines Add-ons etwas ausgibt (Knüppel, Modus, Höhe, Position, Tempo, Gieren), zählt die Rennrunde als Runde mit Flughilfe: Grund other mit der Id der Flughilfe als Gegenstand (Zeichen [a-z0-9_:./-], höchstens 48). Hält sie Höhe oder Position, ohne dass der Pilot selbst Halten geschaltet hat, steht nur sie in der Wertung, nicht „Höhe halten war an“. Kräfte aus registerForces zählen nicht.

Gimbal camera and HUDGimbal-Kamera und HUD

PropwashClientApi.registerGimbal(id, provider) sets the world attitude of the FPV camera (also for spectators); GimbalModel is a ready-made stabilised 3-axis gimbal. PropwashClientApi.registerHudElement(id, element) draws own HUD elements with FlightTelemetry inside the picture area and may replace the Propwash OSD. PropwashClientApi.input() reads sticks and accessory actions.

PropwashClientApi.registerGimbal(id, anbieter) setzt die Weltlage der FPV-Kamera (auch für Zuschauer); GimbalModel ist ein fertiger stabilisierter 3-Achsen-Gimbal. PropwashClientApi.registerHudElement(id, element) zeichnet eigene HUD-Elemente mit FlightTelemetry im Bildbereich und darf das Propwash-OSD ersetzen. PropwashClientApi.input() liest Knüppel und Zubehör-Aktionen.

Events

Event and listenerEvent und ListenerThreadWhenWann
PART_REGISTERED
onPartRegistered(PartDefinition definition)
init thread of the registering modInit-Thread des anmeldenden Modsafter every part an add-on registers (Java or JSON)nach jedem Teil, das ein Add-on anmeldet (Java oder JSON)
REGISTRY_FROZEN
onRegistryFrozen(PartRegistry registry)
init or main threadInit- oder Hauptthreadexactly once, when the part registry freezesgenau einmal, wenn das Teile-Register einfriert
DRONE_ASSEMBLED
onDroneAssembled(ServerPlayer player, ItemStack drone, DroneData data)
serverServera drone was taken out of the workbench outputeine Drohne wurde aus dem Werkbank-Ausgang genommen
DRONE_DISASSEMBLED
onDroneDisassembled(ServerPlayer player, DroneData data)
serverServera drone was taken apart in the workbencheine Drohne wurde in der Werkbank zerlegt
BUILD_IMPORTED
onBuildImported(ServerPlayer player, ImportRequest request, ImportResult result)
serverServerafter every import request, also rejected onesnach jeder Import-Anfrage, auch abgelehnten
ESC_BURNED
onEscBurned(ServerPlayer player, ItemStack drone, DroneData data, int cells)
serverServerthe ESC of a flown drone burned out because the battery has more cells than the stack takes (only with crash damage on)der ESC einer geflogenen Drohne ist durchgebrannt, weil der Akku mehr Zellen hat, als der Stack verträgt (nur mit Crash-Schaden an)
DRONE_HIT_ENTITY
onDroneHitEntity(EntityHit hit)
serverServera flown drone hits a living entity and the game rules allow it; damage and knockback can be changed (since 1.6)eine geflogene Drohne trifft ein Lebewesen und die Spielregeln erlauben es; Schaden und Rückstoß sind änderbar (seit 1.6)
DRONE_DAMAGED
onDroneDamaged(DroneDamage damage)
serverServera drone takes damage from mobs, projectiles, explosions or players; the amount can be changed or the damage cancelled (since 1.6)eine Drohne nimmt Schaden durch Mobs, Geschosse, Explosionen oder Spieler; die Menge ist änderbar, der Schaden abbrechbar (seit 1.6)
ACCESSORY_ACTION
onAccessoryAction(ServerPlayer player, Entity drone, int number, boolean pressed)
serverServerthe pilot presses or releases accessory action 1–4 (checked by the server, since 1.6)der Pilot drückt oder lässt Zubehör-Aktion 1–4 los (vom Server geprüft, seit 1.6)
Java
PropwashEvents.DRONE_ASSEMBLED.register((player, drone, data) ->
        ExampleStats.built(player, data.build()));

Register listeners in the open phase. fire is internal.

Listener in der offenen Phase anmelden. fire ist intern.

Stability and versionsStabilität und Versionen

  • SemVer: the major version changes with every incompatible change, the minor version with additions, the patch version with fixes.SemVer: Die Hauptversion steigt bei jeder inkompatiblen Änderung, die Nebenversion bei Ergänzungen, der Patch bei Fehlerbehebungen.
  • Stable: api.* plus the released internal types ConfigTab, ConfigTabHost, StepSlider, Palette, Metrics, ThemeText, ThemeDraw, ThemedWidgets, ThemedScreen, FlatButton, ScrollPanel, TextField and guide.scene.*. Everything else is internal and may change in any version.Stabil: api.* und die freigegebenen internen Typen ConfigTab, ConfigTabHost, StepSlider, Palette, Metrics, ThemeText, ThemeDraw, ThemedWidgets, ThemedScreen, FlatButton, ScrollPanel, TextField und guide.scene.*. Alles andere ist intern und darf sich in jeder Version ändern.
  • Records: definitions only through builders; value records keep their canonical constructor (a new component comes with an extra constructor).Records: Definitionen nur über Builder; Wertrecords behalten ihren kanonischen Konstruktor (eine neue Komponente kommt mit einem zusätzlichen Konstruktor).
  • Enums get new constants at the end in minor versions, so always switch with a default. Interfaces only get new methods with a default implementation.Enums bekommen in Nebenversionen neue Konstanten am Ende, also switch immer mit default. Interfaces bekommen neue Methoden nur mit Default-Implementierung.
  • JSON "format": 1 gains new optional fields in minor versions. Anything deprecated is announced at least one minor version ahead and only removed in the next major version.JSON "format": 1 bekommt in Nebenversionen neue optionale Felder. Veraltetes wird mindestens eine Nebenversion vorher angekündigt und erst in der nächsten Hauptversion entfernt.

API versionsAPI-Versionen

APIAddedNeu
1.0Parts in Java or JSON, items, presets, builds, paint, tune groups, OSD layouts, server-checked import, events, workbench buttons with child screens, configurator tabs, Flight School scenes.Teile in Java oder JSON, Items, Presets, Baupläne, Lack, Tuning-Gruppen, OSD-Layouts, vom Server geprüfter Import, Events, Werkbank-Knöpfe mit Unterfenstern, Konfigurator-Tabs, Flugschul-Szenen.
1.1BuildAccess#cameraView(spec): the FPV camera and prop discs as Propwash draws them.BuildAccess#cameraView(spec): FPV-Kamera und Prop-Scheiben, wie Propwash sie zeichnet.
1.2A creative tab per add-on (PropwashApi.extensionTab(ns)), power_w and usable_fraction for the energy model, all flight times in BuildStats.Ein Kreativ-Tab je Add-on (PropwashApi.extensionTab(ns)), power_w und usable_fraction für das Energiemodell, alle Flugzeiten in BuildStats.
1.3Flight School links for items of any namespace (linkFlightSchoolItems, linkFlightSchoolParts, FlightSchoolScenes).Flugschul-Verknüpfungen für Items jedes Namensraums (linkFlightSchoolItems, linkFlightSchoolParts, FlightSchoolScenes).
1.4Chest loot without loader code (PropwashApi.loot()), rarity in JSON, more analysis figures, ESC_BURNED, part condition and paint cost, batteries with partial charge, the NAME import layer and new drones from the client, Propwash’s own JSON codec, language texts on the server, workbench header space and icon buttons, camera field of view from camera_fov_deg and the uptilt range of the camera mount.Truhen-Beute ohne Loader-Code (PropwashApi.loot()), Seltenheit im JSON, mehr Analysewerte, ESC_BURNED, Teilezustand und Lackkosten, Akkus mit Teilladung, Import-Ebene NAME und neue Drohnen vom Client, Propwashs eigener JSON-Codec, Sprachtexte auf dem Server, Platz im Werkbank-Kopf und Symbol-Knöpfe, Kamera-Sichtfeld aus camera_fov_deg und der Uptilt-Bereich des Kamerahalters.
1.5Three new built-in Flight School lessons for item links: FlightSchoolScenes.TRICKS (freestyle tricks, after RACE), FlightSchoolScenes.GRIPPER (gripper, after ACTION_CAM) and FlightSchoolScenes.MODE_3D (3D mode, after HOLD), all in FlightSchoolScenes.ALL. Props can declare their reverse thrust for 3D mode: PropDefinition#reverseThrust(), builder reverseThrust(r), JSON reverse_thrust.Drei neue eingebaute Flugschul-Lektionen für Item-Verknüpfungen: FlightSchoolScenes.TRICKS (Freestyle-Tricks, nach RACE), FlightSchoolScenes.GRIPPER (Greifer, nach ACTION_CAM) und FlightSchoolScenes.MODE_3D (3D-Modus, nach HOLD), alle in FlightSchoolScenes.ALL. Props können ihren Umkehrschub für den 3D-Modus angeben: PropDefinition#reverseThrust(), Builder reverseThrust(r), JSON reverse_thrust.
1.6Building blocks for the add-ons Mob Defense and a cinematic camera drone: the front accessory mount (AccessorySlot.FRONT), accessory behaviours with synchronised inventories and accessory actions 1–4 (PropwashApi.accessories()), drone queries, impulses and pushes (PropwashApi.drones()), flight assists and force providers inside the flight model (PropwashApi.flight()), the events DRONE_HIT_ENTITY, DRONE_DAMAGED and ACCESSORY_ACTION, and on the client HUD elements, gimbal cameras and input (registerHudElement, registerGimbal, input()). Network protocol 16. Behaviour: an add-on flight assist that outputs anything makes a race lap an assisted lap (reason other with the assist’s id), forces from registerForces do not; the Accessories window never takes shulker boxes or drone items with accessory contents of their own; a release reaches onAction only while the action is held.Bausteine für die Add-ons Mob Defense und eine Kino-Drohne: die Zubehör-Halterung vorne (AccessorySlot.FRONT), Zubehör-Verhalten mit synchronisierten Inventaren und Zubehör-Aktionen 1–4 (PropwashApi.accessories()), Drohnen-Abfragen, Stöße und Schübe (PropwashApi.drones()), Flughilfen und Kräfte im Flugmodell (PropwashApi.flight()), die Events DRONE_HIT_ENTITY, DRONE_DAMAGED und ACCESSORY_ACTION und im Client HUD-Elemente, Gimbal-Kameras und Eingaben (registerHudElement, registerGimbal, input()). Netzprotokoll 16. Verhalten: Eine Flughilfe eines Add-ons, die etwas ausgibt, macht eine Rennrunde zur Runde mit Flughilfe (Grund other mit der Id der Flughilfe), Kräfte aus registerForces nicht; das Fenster Zubehör nimmt nie Shulkerkisten oder Drohnen-Items mit eigenem Zubehör-Inhalt an; ein Loslassen erreicht onAction nur, solange die Aktion gehalten ist.

Propwash 0.5.2 ships API 1.6.0 and network protocol 16.

Propwash 0.5.2 bringt API 1.6.0 und Netzwerkprotokoll 16.

MultiplayerMehrspieler

  • When a player connects, Propwash compares the Propwash version, the network protocol, the add-on list and the part lists of server and client (ids and a short hash per definition). If the server has parts the player lacks (or a different version of them), or the protocol differs, the player cannot join: the flight model runs on the client and has to see the same data. The player then sees the join overview with one card per add-on – which version to install, update or remove, and a link from your loader metadata (see Name, version and homepage); the server log lists the same differences.Beim Verbinden vergleicht Propwash Version, Netzprotokoll, Add-on-Liste und Teile-Listen von Server und Client (Ids und ein kurzer Hash je Definition). Fehlen dem Spieler Teile des Servers (oder hat er sie in anderer Version) oder weicht das Protokoll ab, kann der Spieler nicht beitreten: Das Flugmodell läuft auf dem Client und muss dieselben Daten sehen. Der Spieler sieht dann die Beitritts-Übersicht mit einer Karte je Add-on – welche Version er installieren, aktualisieren oder entfernen soll, und einen Link aus deinen Loader-Metadaten (siehe Name, Version und Homepage); das Server-Log nennt dieselben Unterschiede.
  • An add-on without parts that only one side has (for example a pure HUD add-on on the client) does not block joining; the overview lists it only as a note.Ein Add-on ohne Teile, das nur eine Seite hat (etwa ein reines HUD-Add-on beim Client), blockiert den Beitritt nicht; die Übersicht nennt es nur als Hinweis.
  • Add-on only on the player’s side (since 0.5.0): a player with your add-on may join a server without it. Propwash switches the add-on off for that session and shows “Add-on inactive on this server”: its parts and cell chemistries, part items, creative tab, presets, workbench extensions, configurator tabs and Flight School scenes are hidden, and imports with its parts are not sent. Ask for the state with PropwashClientApi.isAddonActive("<modid>") (API 1.5). For this to work: create every id (parts, items, presets, workbench extensions, tabs <modid>:…, scenes <modid>:…) in the namespace of your mod id; register your own items, blocks or entities only if you must and hide directly registered items while the add-on is inactive; register network channels as optional (NeoForge: event.registrar("1").optional()) and check before sending (connection.hasChannel(…), Fabric ClientPlayNetworking.canSend(…)); never send anything about your parts yourself – use sendImport/sendCreateDrone. Server-side features (commands, loot, recipes, server events) only run where the add-on is installed anyway.Add-on nur beim Spieler (seit 0.5.0): Ein Spieler mit deinem Add-on darf einem Server ohne es beitreten. Propwash schaltet das Add-on für diese Sitzung ab und zeigt „Add-on auf diesem Server inaktiv“: Seine Teile und Zellchemien, Teil-Items, Kreativ-Tab, Presets, Werkbank-Erweiterungen, Konfigurator-Tabs und Flugschul-Szenen sind ausgeblendet, Importe mit seinen Teilen werden nicht gesendet. Den Zustand fragst du mit PropwashClientApi.isAddonActive("<modid>") ab (API 1.5). Damit das klappt: Lege jede Id (Teile, Items, Presets, Werkbank-Erweiterungen, Tabs <modid>:…, Szenen <modid>:…) im Namensraum deiner Mod-Id an; registriere eigene Items, Blöcke oder Entities nur, wenn es sein muss, und blende direkt registrierte Items aus, solange das Add-on inaktiv ist; melde Netzwerk-Kanäle optional an (NeoForge: event.registrar("1").optional()) und prüfe vor dem Senden (connection.hasChannel(…), Fabric ClientPlayNetworking.canSend(…)); sende nie selbst etwas über deine Teile – nimm sendImport/sendCreateDrone. Server-Funktionen (Befehle, Beute, Rezepte, Server-Events) laufen ohnehin nur dort, wo das Add-on installiert ist.
  • If an add-on is removed from a world, drones with its parts survive: Propwash keeps the unknown id in a placeholder part (not armable, tooltip “Unknown part”). When the add-on comes back, the drone is complete again.Wird ein Add-on aus einer Welt entfernt, bleiben Drohnen mit seinen Teilen erhalten: Propwash behält die unbekannte Id in einem Platzhalter (nicht scharfschaltbar, Tooltip „Unbekanntes Bauteil“). Kommt das Add-on zurück, ist die Drohne wieder vollständig.

Example add-onBeispiel-Add-on

The Propwash source code contains a complete example add-on in the namespace propwash_example (common/src/devtest/java/de/lardtom/propwash/dev/example/, MIT licensed) that uses only the API: version check, events, three parts in Java (2806.5 motor, 13″ carbon prop, stretched-X race frame with its own arms), five parts and a cell chemistry in JSON, items, a preset, its own creative tab, a workbench button with a child screen (preview, part check, import), a configurator tab (OSD layout via the clipboard, rates on the held drone) and a Flight School scene linked to its frames.

Der Propwash-Quellcode enthält ein vollständiges Beispiel-Add-on im Namensraum propwash_example (common/src/devtest/java/de/lardtom/propwash/dev/example/, MIT-lizenziert), das nur die API benutzt: Versionsprüfung, Events, drei Teile in Java (Motor 2806,5, 13″-Carbon-Prop, Stretched-X-Race-Rahmen mit eigenen Armen), fünf Teile und eine Zellchemie in JSON, Items, ein Preset, ein eigener Kreativ-Tab, ein Werkbank-Knopf mit Kind-Fenster (Vorschau, Teile-Check, Import), ein Konfigurator-Tab (OSD-Layout über die Zwischenablage, Rates auf die gehaltene Drohne) und eine Flugschul-Szene, die mit seinen Rahmen verknüpft ist.

Its deadcat frame as JSON is shown on the parts format page. Propwash: Just More Parts is the first real add-on built the same way.

Sein Deadcat-Rahmen als JSON steht auf der Seite zum Teile-Format. Propwash: Just More Parts ist das erste echte Add-on, das genauso gebaut ist.

ChecklistCheckliste

  • requireVersion at start-up, dependency propwash >= 0.5.2 in both loader metadata files, side = "BOTH".requireVersion beim Start, Abhängigkeit propwash >= 0.5.2 in beiden Loader-Metadaten, side = "BOTH".
  • Parts and items in the common init, client extensions in the client init.Teile und Items in der gemeinsamen Init, Client-Erweiterungen in der Client-Init.
  • Every id: path only [a-z0-9_.-], unique across all categories, namespace not propwash.Jede Id: Pfad nur [a-z0-9_.-], eindeutig über alle Kategorien, Namensraum nicht propwash.
  • An item icon assets/<ns>/items/<path>.json and a name part.<ns>.<path> for every part, in English and German.Ein Item-Symbol assets/<ns>/items/<pfad>.json und ein Name part.<ns>.<pfad> für jedes Teil, auf Englisch und Deutsch.
  • New mounts appear on at least one frame and one part (otherwise Propwash warns when the registry freezes).Neue Halterungen tauchen bei mindestens einem Rahmen und einem Teil auf (sonst warnt Propwash beim Einfrieren).
Message or behaviourMeldung oder VerhaltenCauseUrsache
IllegalStateException … Propwash-API …Propwash too old or a new major versionPropwash zu alt oder neue Hauptversion
IllegalStateException on registerIllegalStateException beim registerregistered after the registry froze (for example from an event or the client init)nach dem Einfrieren angemeldet (zum Beispiel aus einem Event oder der Client-Init)
Start-up stops with a list of JSON filesStart bricht mit einer Liste von JSON-Dateien aba required field is missing or a value is invalid; the message names file and fieldPflichtfeld fehlt oder Wert ungültig; die Meldung nennt Datei und Feld
Disconnected: parts differVerbindung getrennt: Teile weichen abthe add-on is missing on one side or has other valuesAdd-on fehlt auf einer Seite oder hat andere Werte
Part missing in the workbenchTeil fehlt in der Werkbankno item registered (registerItems forgotten)kein Item angemeldet (registerItems vergessen)
Import returns NO_TARGETImport liefert NO_TARGETworkbench not open or no drone in the main handWerkbank nicht offen oder keine Drohne in der Haupthand
Import returns BUSYImport liefert BUSYmore than two requests per secondmehr als zwei Anfragen pro Sekunde

LicenseLizenz

From Propwash 0.5.0 on, the add-on API (de.lardtom.propwash.api and its subpackages), the example add-on and the documentation are under the MIT License: your add-on may use them freely, copy from them and have any license of its own. The rest of Propwash is source-available under the PolyForm Shield License 1.0.0 with additional permissions: whoever takes code from it passes on the license with its Required Notice line and must not use it to compete with Propwash. LICENSE-MIT lists the exact files; the texts in LICENSE, LICENSE-MIT and NOTICE in the source repository are binding. Propwash 0.4.0 and older stay All Rights Reserved.

Ab Propwash 0.5.0 stehen die Add-on-API (de.lardtom.propwash.api samt Unterpaketen), das Beispiel-Add-on und die Doku unter der MIT-Lizenz: Dein Add-on darf sie frei benutzen, daraus kopieren und selbst jede Lizenz haben. Der übrige Propwash-Code ist unter der PolyForm Shield License 1.0.0 mit Zusatzrechten einsehbar: Wer daraus etwas übernimmt, gibt die Lizenz samt der Zeile Required Notice mit und darf damit nicht mit Propwash konkurrieren. LICENSE-MIT nennt die genauen Dateien; verbindlich sind die Texte in LICENSE, LICENSE-MIT und NOTICE im Quellcode-Repository. Propwash 0.4.0 und älter bleiben All Rights Reserved.