Update script to write to vendor/hytale-server
This commit is contained in:
416
docs/13-utilities.md
Normal file
416
docs/13-utilities.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user