417 lines
9.6 KiB
Markdown
417 lines
9.6 KiB
Markdown
# 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:
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// Transform with position and rotation
|
|
Transform transform = new Transform(position, rotation);
|
|
Vector3d worldPos = transform.toWorld(localPos);
|
|
Vector3d localPos = transform.toLocal(worldPos);
|
|
```
|
|
|
|
### Location
|
|
|
|
```java
|
|
// Location combines world, position, and rotation
|
|
Location loc = new Location(world, position, yaw, pitch);
|
|
World world = loc.getWorld();
|
|
Vector3d pos = loc.getPosition();
|
|
```
|
|
|
|
### Math Utilities
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```json
|
|
{
|
|
"logLevels": {
|
|
"com.example.myplugin": "DEBUG",
|
|
"com.hypixel.hytale.server": "INFO"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Functional Interfaces
|
|
|
|
### Common Functional Interfaces
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
// Use with caution - for advanced use cases only
|
|
UnsafeUtil.allocateInstance(MyClass.class);
|
|
```
|
|
|
|
## Exception Handling
|
|
|
|
### SneakyThrow
|
|
|
|
For throwing checked exceptions without declaring them:
|
|
|
|
```java
|
|
// 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
|