HyCodeYourTale

Dependencies

Dependencies

Detailní dokumentace k systému závislostí a verzování v Hytale pluginech.

---

Přehled

Dependency Types:

├── Dependencies (Povinné - plugin se nenačte bez nich)

├── OptionalDependencies (Volitelné - plugin funguje i bez nich)

└── LoadBefore (Pořadí načítání)

---

Semver (Semantic Versioning)

Hytale používá standardní semantic versioning:

public class Semver {
private final long major; // Breaking changes
private final long minor; // New features, backward compatible
private final long patch; // Bug fixes
@Nullable
private final String preRelease; // Pre-release tag (alpha, beta, rc)
@Nullable
private final String buildMetadata; // Build metadata

public static Semver fromString(String str) {
// Parsuje "1.2.3", "1.2.3-alpha", "1.2.3-rc.1+build.123"
}
}

Formát Verze

MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]

Příklady:
1.0.0
1.2.3
2.0.0-alpha
2.0.0-beta.1
2.0.0-rc.1+build.456

Pravidla Verzování

| Změna | Význam | Příklad |
|-------|--------|---------|
| MAJOR | Breaking changes | 1.0.0 → 2.0.0 |
| MINOR | Nové funkce (zpětně kompatibilní) | 1.0.0 → 1.1.0 |
| PATCH | Bug fixy | 1.0.0 → 1.0.1 |

---

SemverRange

Definuje rozsah akceptovatelných verzí:

public class SemverRange implements SemverSatisfies {
public static final SemverRange WILDCARD = new SemverRange(new SemverSatisfies[0], true);

private final SemverSatisfies[] comparators;
private final boolean and; // AND vs OR kombinace

@Override
public boolean satisfies(Semver semver) {
if (this.and) {
// Všechny musí být splněny
for (SemverSatisfies comparator : this.comparators) {
if (!comparator.satisfies(semver)) {
return false;
}
}
return true;
} else {
// Alespoň jedna musí být splněna
for (SemverSatisfies comparator : this.comparators) {
if (comparator.satisfies(semver)) {
return true;
}
}
return false;
}
}
}

---

Syntaxe SemverRange

Základní Operátory

| Syntaxe | Význam | Příklad |
|---------|--------|---------|
| | Jakákoliv verze | = všechny |
| 1.2.3 | Přesně tato verze | 1.2.3 |
| >1.2.3 | Větší než | >1.2.3 = 1.2.4+ |
| >=1.2.3 | Větší nebo rovno | >=1.2.3 = 1.2.3+ |
| <1.2.3 | Menší než | <1.2.3 = max 1.2.2 |
| <=1.2.3 | Menší nebo rovno | <=1.2.3 = max 1.2.3 |

Range Operátory

| Syntaxe | Význam | Ekvivalent |
|---------|--------|------------|
| 1.2.3 - 2.3.4 | Inclusive range | >=1.2.3 <=2.3.4 |
| ~1.2.3 | Patch-level changes | >=1.2.3 <1.3.0 |
| ^1.2.3 | Compatible with | >=1.2.3 <2.0.0 |

X-Range

| Syntaxe | Význam | Ekvivalent |
|---------|--------|------------|
| 1.x | Any minor | >=1.0.0 <2.0.0 |
| 1.2.x | Any patch | >=1.2.0 <1.3.0 |
| 1.* | Any minor | >=1.0.0 <2.0.0 |

OR Kombinace

1.0.0 || 2.0.0     // Verze 1.0.0 NEBO 2.0.0
>=1.0.0 || <0.5.0 // Verze 1.0.0+ NEBO pod 0.5.0

---

SemverRange Parsing

Interní implementace:

@Nonnull
public static SemverRange fromString(String str) {
str = str.trim();

// Wildcard
if (str.isBlank() || "*".equals(str)) {
return WILDCARD;
}

// OR kombinace
String[] split = str.split("\\|\\|");
SemverSatisfies[] comparators = new SemverSatisfies[split.length];

for (int i = 0; i < split.length; i++) {
String subRange = split[i].trim();

if (subRange.contains(" - ")) {
// Hyphen range: 1.2.3 - 2.3.4
String[] range = subRange.split(" - ");
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, Semver.fromString(range[0])),
new SemverComparator(ComparisonType.LTE, Semver.fromString(range[1]))
}, true);
}
else if (subRange.charAt(0) == '~') {
// Tilde range: ~1.2.3
Semver semver = Semver.fromString(subRange.substring(1));
// >=1.2.3 <1.3.0
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, semver),
new SemverComparator(ComparisonType.LT,
new Semver(semver.getMajor(), semver.getMinor() + 1, 0, null, null))
}, true);
}
else if (subRange.charAt(0) == '^') {
// Caret range: ^1.2.3
Semver semver = Semver.fromString(subRange.substring(1));
if (semver.getMajor() > 0) {
// >=1.2.3 <2.0.0
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, semver),
new SemverComparator(ComparisonType.LT,
new Semver(semver.getMajor() + 1, 0, 0, null, null))
}, true);
}
// ... special handling for 0.x versions
}
else if (hasPrefix(subRange)) {
// Comparator: >=1.2.3, >1.2.3, etc.
comparators[i] = SemverComparator.fromString(subRange);
}
// ...
}

return new SemverRange(comparators, false); // OR between ranges
}

---

Typy Závislostí

Dependencies (Povinné)

Plugin se nenačte pokud závislost chybí nebo nemá správnou verzi:

{
"Dependencies": {
"Hytale:DamageModule": "*",
"Hytale:TeleportPlugin": ">=1.0.0",
"MyCompany:CoreLib": "^2.0.0"
}
}

// Validace při načítání
for (Entry entry : manifest.getDependencies().entrySet()) {
PluginIdentifier id = entry.getKey();
SemverRange expectedVersion = entry.getValue();

PluginBase dependency = plugins.get(id);
if (dependency == null) {
throw new MissingPluginDependencyException("Dependency not found: " + id);
}

if (!dependency.getManifest().getVersion().satisfies(expectedVersion)) {
throw new MissingPluginDependencyException(
String.format("Version of dependency '%s'(%s) does not satisfy '%s'",
id, dependency.getManifest().getVersion(), expectedVersion)
);
}
}

OptionalDependencies (Volitelné)

Plugin se načte i bez těchto závislostí:

{
"OptionalDependencies": {
"Hytale:BedsPlugin": "*",
"ThirdParty:DiscordIntegration": ">=1.0.0"
}
}

Použití v kódu:

@Override
protected void setup() {
PluginManager pm = PluginManager.get();

// Kontrola volitelné závislosti
PluginBase bedsPlugin = pm.getPlugin(new PluginIdentifier("Hytale", "BedsPlugin"));
if (bedsPlugin != null && bedsPlugin.isEnabled()) {
// Integrace s BedsPlugin
setupBedsIntegration();
getLogger().info("BedsPlugin integration enabled");
}

// Nebo pomocí hasPlugin s verzí
if (pm.hasPlugin(new PluginIdentifier("ThirdParty", "DiscordIntegration"),
SemverRange.fromString(">=1.0.0"))) {
setupDiscordIntegration();
}
}

LoadBefore

Plugin se načte před specifikovanými pluginy:

{
"LoadBefore": {
"OtherPlugin:UISystem": "*"
}
}

Použití:

  • Registrace event handlerů před jinými pluginy

  • Inicializace sdílených služeb

  • Nastavení defaultů před overrides
  • ---

    Load Order

    PluginManager vypočítá pořadí načítání na základě:

    1. Dependencies - Plugin musí být načten po svých závislostech
    2. LoadBefore - Plugin musí být načten před specifikovanými

    // Z PluginManager
    this.loadOrder = PendingLoadPlugin.calculateLoadOrder(pending);

    for (PendingLoadPlugin pendingLoadPlugin : this.loadOrder) {
    PluginBase plugin = pendingLoadPlugin.load();
    if (plugin != null) {
    this.plugins.put(plugin.getIdentifier(), plugin);
    }
    }

    Příklad Load Order

    Manifest A:
    Dependencies: []

    Manifest B:
    Dependencies: [A]

    Manifest C:
    Dependencies: [A, B]
    LoadBefore: [D]

    Manifest D:
    Dependencies: [A]

    Load Order: A → B → C → D

    ---

    Server Version

    Kontrola kompatibility se serverem:

    {
    "ServerVersion": ">=0.4.0 <1.0.0"
    }

    private void validatePluginDeps(PendingLoadPlugin plugin, ...) {
    Semver serverVersion = ManifestUtil.getVersion();
    SemverRange serverVersionRange = plugin.getManifest().getServerVersion();

    if (serverVersionRange != null && serverVersion != null) {
    if (!serverVersionRange.satisfies(serverVersion)) {
    throw new MissingPluginDependencyException(
    String.format("Failed to load '%s' because version of server does not satisfy '%s'!",
    plugin.getIdentifier(), serverVersion)
    );
    }
    }
    }

    ---

    Vestavěné Moduly Hytale

    Hytale:DamageModule

    {
    "Dependencies": {
    "Hytale:DamageModule": "*"
    }
    }

    Poskytuje:

  • DamageEvent

  • DeathEvent

  • Damage calculation system
  • Hytale:TeleportPlugin

    {
    "Dependencies": {
    "Hytale:TeleportPlugin": "*"
    }
    }

    Poskytuje:

  • /spawn, /tp, /warp příkazy

  • TeleportHistory komponenta

  • Warp management systém
  • Hytale:BedsPlugin

    {
    "Dependencies": {
    "Hytale:BedsPlugin": "*"
    }
    }

    Poskytuje:

  • Sleep systém

  • Bed interakce

  • Spawn point setting
  • Hytale:EntityModule

    {
    "Dependencies": {
    "Hytale:EntityModule": "*"
    }
    }

    Poskytuje:

  • Entity registry

  • Component types

  • Entity systems

---

Cyklické Závislosti

Cyklické závislosti jsou zakázány a způsobí chybu:

A → B → C → A  (CHYBA!)

PluginManager detekuje cykly při výpočtu load order.

---

Runtime Operace

Načtení Pluginu

PluginManager pm = PluginManager.get();

// Načíst plugin
boolean success = pm.load(new PluginIdentifier("MyCompany", "MyPlugin"));

// Plugin musí mít splněné všechny závislosti

Vyčtení Pluginu

// Unload (odpojí a vyčistí)
boolean success = pm.unload(new PluginIdentifier("MyCompany", "MyPlugin"));

Reload Pluginu

// Unload + Load
boolean success = pm.reload(new PluginIdentifier("MyCompany", "MyPlugin"));

Kontrola Závislosti

PluginManager pm = PluginManager.get();

// Má plugin s verzí?
boolean hasPlugin = pm.hasPlugin(
new PluginIdentifier("Hytale", "TeleportPlugin"),
SemverRange.fromString(">=1.0.0")
);

// Získat plugin
PluginBase plugin = pm.getPlugin(new PluginIdentifier("Hytale", "TeleportPlugin"));
if (plugin != null && plugin.isEnabled()) {
// Použij plugin
}

---

Class Loading

Pluginy mohou přistupovat k třídám svých závislostí:

// PluginBridgeClassLoader
public Class loadClass0(String name, PluginClassLoader pluginClassLoader, PluginManifest manifest) {
// 1. Nejprve hledej v Dependencies
for (PluginIdentifier id : manifest.getDependencies().keySet()) {
PluginBase pluginBase = plugins.get(id);
Class loadClass = tryGetClass(name, pluginClassLoader, pluginBase);
if (loadClass != null) {
return loadClass;
}
}

// 2. Pak v OptionalDependencies
for (PluginIdentifier id : manifest.getOptionalDependencies().keySet()) {
if (!manifest.getDependencies().containsKey(id)) {
PluginBase pluginBase = plugins.get(id);
if (pluginBase != null) {
Class loadClass = tryGetClass(name, pluginClassLoader, pluginBase);
if (loadClass != null) {
return loadClass;
}
}
}
}

// 3. Nakonec v ostatních pluginech
for (Entry entry : plugins.entrySet()) {
if (!manifest.getDependencies().containsKey(entry.getKey()) &&
!manifest.getOptionalDependencies().containsKey(entry.getKey())) {
Class loadClass = tryGetClass(name, pluginClassLoader, entry.getValue());
if (loadClass != null) {
return loadClass;
}
}
}

throw new ClassNotFoundException();
}

---

Best Practices

1. Používej SemverRange Správně

// Špatně - příliš striktní
{
"Dependencies": {
"SomePlugin": "1.2.3" // Jen přesně 1.2.3
}
}

// Lépe - kompatibilní verze
{
"Dependencies": {
"SomePlugin": "^1.2.3" // 1.2.3 až <2.0.0
}
}

// Ještě lépe - minimální verze
{
"Dependencies": {
"SomePlugin": ">=1.2.3" // 1.2.3 nebo novější
}
}

2. Kontroluj Optional Dependencies

@Override
protected void setup() {
// VŽDY kontroluj null
PluginBase optDep = PluginManager.get().getPlugin(
new PluginIdentifier("Optional", "Feature")
);

if (optDep != null && optDep.isEnabled()) {
// Bezpečné použití
}
}

3. Dokumentuj Závislosti

{
"Description": "My Plugin - requires DamageModule for combat features, optionally uses BedsPlugin for spawn points",
"Dependencies": {
"Hytale:DamageModule": "*"
},
"OptionalDependencies": {
"Hytale:BedsPlugin": "*"
}
}

---

Shrnutí

| Typ | Chování | Použití |
|-----|---------|---------|
| Dependencies | Povinné, blokující | Nezbytné funkce |
| OptionalDependencies | Volitelné | Rozšíření funkcí |
| LoadBefore | Pořadí načítání | Inicializace před jinými |
| ServerVersion | Kompatibilita serveru | API verze |

| Operátor | Syntaxe | Příklad |
|----------|---------|---------|
| Wildcard | * | Všechny verze |
| Greater | > | >1.0.0 |
| Greater/Equal | >= | >=1.0.0 |
| Less | < | <2.0.0 |
| Less/Equal | <= | <=2.0.0 |
| Tilde | ~ | ~1.2.3 = >=1.2.3 <1.3.0 |
| Caret | ^ | ^1.2.3 = >=1.2.3 <2.0.0 |
| Range | - | 1.0.0 - 2.0.0 |
| OR | \|\| | 1.0.0 \|\| 2.0.0 |

Last updated: 20. ledna 2026