Files
hytale-server/docs/13-utilities.md

9.6 KiB

Utilities and Common APIs

The Hytale server provides a comprehensive set of utility classes and common APIs. This guide covers the most useful utilities for mod development.

Math Utilities

Vectors

The math package provides vector classes for 2D and 3D operations:

// 3D Vectors
Vector3d posD = new Vector3d(1.5, 2.5, 3.5);  // double precision
Vector3f posF = new Vector3f(1.5f, 2.5f, 3.5f);  // float precision
Vector3i posI = new Vector3i(1, 2, 3);  // integer

// 2D Vectors
Vector2d pos2D = new Vector2d(1.5, 2.5);
Vector2i pos2I = new Vector2i(1, 2);

// Vector operations
Vector3d a = new Vector3d(1, 2, 3);
Vector3d b = new Vector3d(4, 5, 6);

Vector3d sum = a.add(b);           // (5, 7, 9)
Vector3d diff = a.subtract(b);      // (-3, -3, -3)
Vector3d scaled = a.multiply(2);    // (2, 4, 6)
double dot = a.dot(b);              // 32
Vector3d cross = a.cross(b);        // (-3, 6, -3)
double length = a.length();         // ~3.74
Vector3d normalized = a.normalize(); // unit vector
double distance = a.distance(b);    // distance between points

Vector Codecs

// Serialization
Vector3d.CODEC  // {"x": 1.0, "y": 2.0, "z": 3.0}
Vector3i.CODEC  // {"x": 1, "y": 2, "z": 3}
Vector2d.CODEC  // {"x": 1.0, "y": 2.0}

Shapes

// Bounding box
Box box = new Box(minPoint, maxPoint);
boolean contains = box.contains(point);
boolean intersects = box.intersects(otherBox);
Vector3d center = box.getCenter();
Vector3d size = box.getSize();

// Sphere/Ellipsoid
Ellipsoid sphere = new Ellipsoid(center, radius, radius, radius);
boolean inSphere = sphere.contains(point);

// Cylinder
Cylinder cyl = new Cylinder(base, height, radius);

Transform

// Transform with position and rotation
Transform transform = new Transform(position, rotation);
Vector3d worldPos = transform.toWorld(localPos);
Vector3d localPos = transform.toLocal(worldPos);

Location

// Location combines world, position, and rotation
Location loc = new Location(world, position, yaw, pitch);
World world = loc.getWorld();
Vector3d pos = loc.getPosition();

Math Utilities

// MathUtil
double clamped = MathUtil.clamp(value, min, max);
double lerp = MathUtil.lerp(start, end, t);
int floor = MathUtil.floor(3.7);  // 3
int ceil = MathUtil.ceil(3.1);    // 4
double wrap = MathUtil.wrap(angle, 0, 360);

// TrigMathUtil
double sin = TrigMathUtil.sin(angle);
double cos = TrigMathUtil.cos(angle);
double atan2 = TrigMathUtil.atan2(y, x);

Collection Utilities

ArrayUtil

// Array operations
String[] combined = ArrayUtil.combine(array1, array2);
int index = ArrayUtil.indexOf(array, element);
boolean contains = ArrayUtil.contains(array, element);
String[] filtered = ArrayUtil.filter(array, predicate);

ListUtil

// List operations
List<String> shuffled = ListUtil.shuffle(list);
List<String> filtered = ListUtil.filter(list, predicate);
Optional<String> random = ListUtil.random(list);
List<List<String>> partitioned = ListUtil.partition(list, 10);

MapUtil

// Map operations
Map<K, V> filtered = MapUtil.filter(map, predicate);
Map<K, V> merged = MapUtil.merge(map1, map2);
<K, V> V getOrCreate(Map<K, V> map, K key, Supplier<V> creator);

WeightedMap

For weighted random selection:

WeightedMap<String> lootTable = new WeightedMap<>();
lootTable.add("common_item", 70);
lootTable.add("rare_item", 25);
lootTable.add("epic_item", 5);

// Random selection based on weights
String selected = lootTable.random();  // 70% common, 25% rare, 5% epic

String Utilities

StringUtil

// String operations
boolean isEmpty = StringUtil.isEmpty(str);
boolean isBlank = StringUtil.isBlank(str);
String trimmed = StringUtil.trim(str);
String capitalized = StringUtil.capitalize("hello");  // "Hello"
String joined = StringUtil.join(list, ", ");
List<String> split = StringUtil.split(str, ",");

// Formatting
String formatted = StringUtil.format("{0} has {1} items", player, count);

FormatUtil

// Number formatting
String formatted = FormatUtil.formatNumber(1234567);  // "1,234,567"
String decimal = FormatUtil.formatDecimal(3.14159, 2);  // "3.14"
String percent = FormatUtil.formatPercent(0.75);  // "75%"

// Time formatting
String duration = FormatUtil.formatDuration(3661000);  // "1h 1m 1s"
String time = FormatUtil.formatTime(timestamp);

Time Utilities

TimeUtil

// Time conversions
long ticks = TimeUtil.secondsToTicks(5);  // 100 ticks
long seconds = TimeUtil.ticksToSeconds(100);  // 5 seconds
long millis = TimeUtil.ticksToMillis(20);  // 1000ms

// Current time
long now = TimeUtil.now();
long serverTick = TimeUtil.currentTick();

// Duration parsing
Duration duration = TimeUtil.parseDuration("1h30m");

Tickable Interface

public interface Tickable {
    void tick();
}

// Implement for objects that update each tick
public class MyTickable implements Tickable {
    @Override
    public void tick() {
        // Update logic
    }
}

Version Management

Semver

// Semantic versioning
Semver version = Semver.parse("1.2.3");
int major = version.getMajor();  // 1
int minor = version.getMinor();  // 2
int patch = version.getPatch();  // 3

// Comparison
boolean isNewer = version.isNewerThan(Semver.parse("1.2.0"));
boolean isCompatible = version.isCompatibleWith(Semver.parse("1.0.0"));

SemverRange

// Version ranges
SemverRange range = SemverRange.parse(">=1.0.0 <2.0.0");
boolean matches = range.matches(Semver.parse("1.5.0"));  // true
boolean matches2 = range.matches(Semver.parse("2.0.0"));  // false

// Common patterns
SemverRange.parse(">=1.0.0");   // 1.0.0 and above
SemverRange.parse("~1.2.3");    // >=1.2.3 <1.3.0
SemverRange.parse("^1.2.3");    // >=1.2.3 <2.0.0

Random Utilities

Random Number Generation

// Thread-safe random
Random random = RandomUtil.getRandom();
int randInt = random.nextInt(100);
double randDouble = random.nextDouble();
boolean randBool = random.nextBoolean();

// Range-based random
int inRange = RandomUtil.nextInt(10, 20);  // 10-19
double inRangeD = RandomUtil.nextDouble(1.0, 5.0);

// Random selection
String selected = RandomUtil.select(list);
String[] selectedMultiple = RandomUtil.select(list, 3);

Logging

Logger Access

// In your plugin
Logger logger = getLogger();

logger.info("Information message");
logger.warn("Warning message");
logger.error("Error message");
logger.debug("Debug message");

// With formatting
logger.info("Player {} joined from {}", playerName, ip);

// With exception
try {
    // ...
} catch (Exception e) {
    logger.error("Operation failed", e);
}

Log Levels

Log levels can be configured per-package in server config:

{
    "logLevels": {
        "com.example.myplugin": "DEBUG",
        "com.hypixel.hytale.server": "INFO"
    }
}

Functional Interfaces

Common Functional Interfaces

// Consumers
Consumer<Player> playerHandler = player -> { /* handle */ };
BiConsumer<Player, String> messageHandler = (player, msg) -> { /* handle */ };

// Suppliers
Supplier<World> worldSupplier = () -> Universe.get().getWorld("default");

// Predicates
Predicate<Entity> isPlayer = entity -> entity instanceof Player;
BiPredicate<Player, String> hasPermission = Player::hasPermission;

// Functions
Function<String, Player> findPlayer = Universe.get()::getPlayer;

Task Scheduling

Task Registry

// Register a recurring task
getTaskRegistry().register(new MyTask());

public class MyTask implements Task {
    @Override
    public void tick() {
        // Called every server tick
    }
    
    @Override
    public int getInterval() {
        return 20;  // Run every 20 ticks (1 second)
    }
}

Delayed Execution

// Schedule for later
getTaskRegistry().runLater(() -> {
    // Execute after delay
}, 100);  // 100 ticks = 5 seconds

// Schedule repeating
getTaskRegistry().runRepeating(() -> {
    // Execute repeatedly
}, 0, 20);  // Start immediately, repeat every 20 ticks

Data Persistence

IndexedStorageFile

For storing data in indexed files:

IndexedStorageFile<MyData> storage = new IndexedStorageFile<>(
    path,
    MyData.CODEC
);

// Write data
storage.write("key1", data1);
storage.write("key2", data2);

// Read data
MyData data = storage.read("key1");

// Check existence
boolean exists = storage.exists("key1");

// Delete
storage.delete("key1");

UUID Utilities

// UUID operations
UUID uuid = UUID.randomUUID();
String uuidString = uuid.toString();
UUID parsed = UUID.fromString(uuidString);

// Offline UUID (name-based)
UUID offlineUUID = UUIDUtil.getOfflineUUID("PlayerName");

Reflection Utilities (Unsafe)

The unsafe package provides low-level utilities:

// Use with caution - for advanced use cases only
UnsafeUtil.allocateInstance(MyClass.class);

Exception Handling

SneakyThrow

For throwing checked exceptions without declaring them:

// Throw checked exception without declaring
SneakyThrow.sneakyThrow(new IOException("Error"));

Best Practices

  1. Use provided utilities - Don't reinvent the wheel
  2. Prefer immutable types - Use Vector3d over mutable alternatives
  3. Use codecs - Serialize with built-in codecs when possible
  4. Handle nulls - Check for null returns from utility methods
  5. Log appropriately - Use correct log levels
  6. Cache computations - Don't recalculate expensive operations
  7. Use thread-safe utilities - RandomUtil is thread-safe
  8. Validate input - Use MathUtil.clamp for ranges
  9. Format consistently - Use FormatUtil for user-facing strings
  10. Test thoroughly - Utility edge cases can cause subtle bugs