Compare commits
32 Commits
da9efdb765
...
a17bb2a732
Author | SHA1 | Date | |
---|---|---|---|
a17bb2a732 | |||
6f2b8b7bbb | |||
307c67d976 | |||
d9b25c960d | |||
34dcc68e15 | |||
f95bc9762f | |||
5c6efaf03d | |||
77a9beb665 | |||
56c154cc96 | |||
242f372819 | |||
1d77cf8f98 | |||
56f4fe46a6 | |||
e2d27e34a5 | |||
5769cff60b | |||
8ef6bead2c | |||
6729f2decc | |||
06a202bf17 | |||
8eb1db919f | |||
e81079479b | |||
d1865900f6 | |||
69e0a8737e | |||
b41d45b3e9 | |||
a2cc4ba6c3 | |||
0b3aac6189 | |||
2f782d7825 | |||
b4902559ac | |||
1b7076f04f | |||
96fdcccdd0 | |||
032f32626e | |||
16ccb84d3b | |||
19324c6247 | |||
67ac2b272e |
55
ADDITIONS.md
55
ADDITIONS.md
@ -4,7 +4,7 @@
|
||||
This document briefly documents what have been added (or removed) regarding modding capabilities or engine behavior(s)
|
||||
|
||||
This document is non-exhaustive, engine contains way more behavior change bits than documented here,
|
||||
but listing all of them will be a hassle, and will pollute actually useful changes.
|
||||
but listing all of them will be a hassle, and will pollute this document.
|
||||
|
||||
---------------
|
||||
|
||||
@ -12,6 +12,8 @@ but listing all of them will be a hassle, and will pollute actually useful chang
|
||||
|
||||
* `treasurechests` now can specify `treasurePool` as array
|
||||
* `damageTable` can be defined directly, without referencing other JSON file (experimental feature)
|
||||
* `environmentStatusEffects` of visitable world parameters now accept `StatModifier`s and not just unique status effects as `String`s
|
||||
* Keep in mind, putting stat modifiers there will blow up original clients due to Json deserializer expecting only strings (despite `StatusController` treating `environmentStatusEffects` as field containing either `StatModifier` or `String`)
|
||||
|
||||
## Biomes
|
||||
* Tree biome placeables now have `variantsRange` (defaults to `[1, 1]`) and `subVariantsRange` (defaults to `[2, 2]`)
|
||||
@ -32,7 +34,6 @@ val color: TileColor = TileColor.DEFAULT
|
||||
```
|
||||
* `item` brush now can accept proper item descriptors (in json object tag),
|
||||
* Previous behavior remains unchanged (if specified as string, creates _randomized_ item, if as object, creates _exactly_ what have been specified)
|
||||
* To stop randomizing as Tiled tileset brush, specify `"dont_randomize"` as anything (e.g. as `""`)
|
||||
* `liquid` brush now can accept 'level' as second argument
|
||||
* Previous behavior is unchanged, `["liquid", "water", true]` will result into infinite water as before, but `["liquid", "water", 0.5, false]` will spawn half-filled water
|
||||
* In tiled, you already can do this using `"quantity"` property
|
||||
@ -43,7 +44,7 @@ val color: TileColor = TileColor.DEFAULT
|
||||
## .terrain
|
||||
|
||||
Please keep in mind that if you use new format or new terrain selectors original clients will
|
||||
probably explode upon joining worlds where new terrain selectors are utilized.
|
||||
explode upon joining worlds where new terrain selectors are utilized.
|
||||
|
||||
* All composing terrain selectors (such as `min`, `displacement`, `rotate`, etc) now can reference other terrain selectors by name (the `.terrain` files) instead of embedding entire config inside them
|
||||
* They can be referenced by either specifying corresponding field as string, or as object like so: `{"name": "namedselector"}`
|
||||
@ -96,13 +97,30 @@ probably explode upon joining worlds where new terrain selectors are utilized.
|
||||
## .matmod
|
||||
* `modId` is no longer essential and can be skipped, or specified as any number in 1 -> 2^31 range, with notes of `materialId` and `liquidId` apply.
|
||||
|
||||
## Monster Definitions
|
||||
|
||||
### New parameters merge rules
|
||||
|
||||
In addition to `add`, `multiply`, `merge` and `override` new merge methods are accepted:
|
||||
* `sub` (a - b)
|
||||
* `divide` (a / b)
|
||||
* `inverse` (b / a)
|
||||
* `min`
|
||||
* `max`
|
||||
* `pow` (a in power of b)
|
||||
* `invpow` (b in power of a)
|
||||
|
||||
---------------
|
||||
|
||||
# Scripting
|
||||
|
||||
* In DamageSource, `sourceEntityId` combination with `rayCheck` has been fixed, and check for tile collision between victim and inflictor (this entity), not between victim and attacker (`sourceEntityId`)
|
||||
* Contexts, where previously only `entity` bindings were available, now have entity-specific bindings exposed
|
||||
* Example: Status controller scripts now get `monster` bindings when running in context of Monster's status controller, etc
|
||||
* `behavior.behavior` third argument (specified commonly as `_ENV`) is ignored and can be omitted (set to nil)
|
||||
* It was used solely to get Lua engine (Lua execution context), and could have been deprecated long time ago even in original engine, because there is now a way in original engine to get Lua engine when binding is called
|
||||
|
||||
### Random
|
||||
## Random
|
||||
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
||||
* Removed `random:addEntropy`
|
||||
|
||||
@ -121,6 +139,30 @@ probably explode upon joining worlds where new terrain selectors are utilized.
|
||||
* Added `animator.hasEffect(effect: string): boolean`
|
||||
* Added `animator.parts(): List<string>`
|
||||
|
||||
## mcontroller
|
||||
|
||||
* Added `mcontroller.collisionPolies(): List<Poly>`, since engine technically supports multiple convex bodies attached to one movement controller
|
||||
* Added `mcontroller.collisionBodies(): List<Poly>`, since engine technically supports multiple convex bodies attached to one movement controller
|
||||
* Added `mcontroller.liquidName(): String?`, returns nil if not touching any liquid
|
||||
* This addition marks `mcontroller.liquidId(): Int` deprecated
|
||||
|
||||
## monster
|
||||
|
||||
* Added `monster.seedNumber(): Long`, use this instead of `monster.seed(): String`
|
||||
* `monster.level(): Double?` returns nil if no monster level was specified
|
||||
* `monster.setDamageParts(parts: Table?)` now accepts nil as equivalent of empty table (consistency fix)
|
||||
* `monster.setPhysicsForces(forces: Table?)` now accepts nil as equivalent of empty table (consistency fix)
|
||||
* `mosnter.setName(name: String?)` now accepts nil to reset custom name
|
||||
|
||||
## status
|
||||
|
||||
* Implemented `status.appliesEnvironmentStatusEffects(): Boolean`, which exists in original engine's code but was never hooked up to Lua bindings
|
||||
* Implemented `status.appliesWeatherStatusEffects(): Boolean`, which exists in original engine's code but was never hooked up to Lua bindings
|
||||
* Implemented `status.setAppliesEnvironmentStatusEffects(should: Boolean)`, which exists in original engine's code but was never hooked up to Lua bindings
|
||||
* Implemented `status.setAppliesWeatherStatusEffects(should: Boolean)`, which exists in original engine's code but was never hooked up to Lua bindings
|
||||
* Added `status.minimumLiquidStatusEffectPercentage(): Double`
|
||||
* Added `status.setMinimumLiquidStatusEffectPercentage(value: Double)`
|
||||
|
||||
## world
|
||||
|
||||
#### Additions
|
||||
@ -244,7 +286,10 @@ _slightly_ different results from execution to execution,
|
||||
such as one microdungeon taking precedence over another microdungeon
|
||||
if they happen to generate in proximity on chunk border (one dungeon generated in chunk A, second generated in chunk B,
|
||||
and they happened to overlap each other),
|
||||
and which one gets placed is determined by who finishes generating first.
|
||||
and which one gets placed is determined by who finishes generating first; as well as case
|
||||
of approaching same chunk in world from different sides (exploring world left to right can yield
|
||||
different generation result when exploring from right to left, and this is not something that can be fixed,
|
||||
unless world is pre-generated in its entirety).
|
||||
|
||||
---------------
|
||||
|
||||
|
@ -23,6 +23,7 @@ val log4jVersion: String by project
|
||||
val guavaVersion: String by project
|
||||
val junitVersion: String by project
|
||||
val sqliteVersion: String by project
|
||||
val picocliVersion: String by project
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
@ -91,6 +92,8 @@ dependencies {
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion")
|
||||
implementation(project(":luna"))
|
||||
|
||||
implementation("info.picocli:picocli:$picocliVersion")
|
||||
|
||||
implementation("org.xerial:sqlite-jdbc:$sqliteVersion")
|
||||
|
||||
implementation("io.netty:netty-transport:$nettyVersion")
|
||||
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
||||
|
||||
kotlinVersion=1.9.10
|
||||
kotlinCoroutinesVersion=1.8.0
|
||||
kommonsVersion=2.17.0
|
||||
kommonsVersion=3.0.1
|
||||
|
||||
ffiVersion=2.2.13
|
||||
lwjglVersion=3.3.0
|
||||
@ -17,3 +17,5 @@ log4jVersion=2.17.1
|
||||
guavaVersion=33.0.0-jre
|
||||
junitVersion=5.8.2
|
||||
sqliteVersion=3.45.3.0
|
||||
|
||||
picocliVersion=4.7.6
|
||||
|
@ -1344,7 +1344,7 @@ public final class StringLib {
|
||||
}
|
||||
} while (idx >= 0);
|
||||
|
||||
context.getReturnBuffer().setTo(bld.toString());
|
||||
context.getReturnBuffer().setTo(ByteString.of(bld.toString()));
|
||||
}
|
||||
|
||||
private static class SuspendedState {
|
||||
|
@ -46,6 +46,9 @@ public class TraversableHashMap<K, V> implements Map<K, V> {
|
||||
private final Set<Map.Entry<K, V>> entrySet;
|
||||
private K firstKey;
|
||||
private K lastKey;
|
||||
// replicate 'next' behavior of reference implementation, where removing key from table does not invalidate it immediately
|
||||
private K lingeringKey;
|
||||
private K lingeringSuccessor;
|
||||
|
||||
/**
|
||||
* Constructs a new empty map.
|
||||
@ -133,10 +136,12 @@ public class TraversableHashMap<K, V> implements Map<K, V> {
|
||||
Entry<K, V> e = entries.remove(key);
|
||||
|
||||
if (e != null) {
|
||||
|
||||
K prevKey = e.getPreviousKey();
|
||||
K nextKey = e.getNextKey();
|
||||
|
||||
lingeringKey = (K) key;
|
||||
lingeringSuccessor = nextKey;
|
||||
|
||||
if (prevKey != null) {
|
||||
entries.get(prevKey).setNextKey(nextKey);
|
||||
} else {
|
||||
@ -202,6 +207,9 @@ public class TraversableHashMap<K, V> implements Map<K, V> {
|
||||
Objects.requireNonNull(key);
|
||||
Entry<K, V> e = entries.get(key);
|
||||
if (e == null) {
|
||||
if (key.equals(lingeringKey))
|
||||
return lingeringSuccessor;
|
||||
|
||||
throw new NoSuchElementException(key.toString());
|
||||
}
|
||||
return e.getNextKey();
|
||||
|
@ -51,3 +51,5 @@ inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reade
|
||||
inline fun <reified T> Gson.fromJson(reader: JsonElement): T? = getAdapter(T::class.java).read(FastJsonTreeReader(reader))
|
||||
|
||||
fun <T> Gson.fromJsonFast(reader: JsonElement, type: Class<T>): T = getAdapter(type).read(FastJsonTreeReader(reader))
|
||||
fun <T> Gson.fromJsonFast(reader: JsonElement, type: TypeToken<T>): T = getAdapter(type).read(FastJsonTreeReader(reader))
|
||||
inline fun <reified T> Gson.fromJsonFast(reader: JsonElement): T = getAdapter(object : TypeToken<T>() {}).read(FastJsonTreeReader(reader))
|
||||
|
@ -13,13 +13,16 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.ClientConfig
|
||||
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.ElementalDamageType
|
||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SpawnerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.UniverseServerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.WorldServerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldServerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemGlobalConfig
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestGlobalConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||
@ -33,7 +36,6 @@ import ru.dbotthepony.kstarbound.defs.world.SystemWorldConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Future
|
||||
@ -118,6 +120,15 @@ object Globals {
|
||||
var shipUpgrades by Delegates.notNull<ShipUpgrades>()
|
||||
private set
|
||||
|
||||
var quests by Delegates.notNull<QuestGlobalConfig>()
|
||||
private set
|
||||
|
||||
var spawner by Delegates.notNull<SpawnerConfig>()
|
||||
private set
|
||||
|
||||
var elementalTypes by Delegates.notNull<ImmutableMap<String, ElementalDamageType>>()
|
||||
private set
|
||||
|
||||
private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()
|
||||
|
||||
val profanityFilter: ImmutableSet<String> by lazy {
|
||||
@ -224,11 +235,14 @@ object Globals {
|
||||
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
||||
tasks.add(load("/ships/shipupgrades.config", ::shipUpgrades))
|
||||
tasks.add(load("/quests/quests.config", ::quests))
|
||||
tasks.add(load("/spawning.config", ::spawner))
|
||||
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture())
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture())
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture())
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture())
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/damage/elementaltypes.config", ::elementalTypes, mapAdapter("/damage/elementaltypes.config")) }.asCompletableFuture())
|
||||
|
||||
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())
|
||||
|
||||
|
@ -1,31 +1,79 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.lib.CoroutineLib
|
||||
import org.classdump.luna.runtime.Coroutine
|
||||
import org.lwjgl.Version
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Option
|
||||
import picocli.CommandLine.Parameters
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.Callable
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
fun main() {
|
||||
Starbound.addArchive(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak"))
|
||||
@Command(
|
||||
name = "client",
|
||||
mixinStandardHelpOptions = true,
|
||||
description = ["Launches game client"]
|
||||
)
|
||||
class StartClientCommand : Callable<Int> {
|
||||
@Option(names = ["-d", "--data"], description = ["Game folder where Starbound is installed.", "Defaults to working directory."])
|
||||
var dataFolder: String = "./"
|
||||
|
||||
/*for (f in File("J:\\Steam\\steamapps\\workshop\\content\\211820").listFiles()!!) {
|
||||
for (f2 in f.listFiles()!!) {
|
||||
if (f2.isFile) {
|
||||
Starbound.addArchive(f2)
|
||||
}
|
||||
@Option(names = ["-s", "--storage"], description = ["Location where to store game files.", "Defaults to game's 'storage' subfolder"])
|
||||
var storageFolder: String? = null
|
||||
|
||||
override fun call(): Int {
|
||||
val data = File(dataFolder)
|
||||
|
||||
if (!data.exists()) {
|
||||
LOGGER.fatal("No such file ${data.absolutePath}")
|
||||
return 1
|
||||
}
|
||||
}*/
|
||||
|
||||
LOGGER.info("Running LWJGL ${Version.getVersion()}")
|
||||
val file = File("${dataFolder}/assets/packed.pak")
|
||||
|
||||
val client = StarboundClient()
|
||||
if (!file.exists()) {
|
||||
LOGGER.fatal("No such file ${file.absolutePath}")
|
||||
return 1
|
||||
}
|
||||
|
||||
Starbound.initializeGame().thenApply {
|
||||
val server = IntegratedStarboundServer(client, File("./storage"))
|
||||
server.channels.createChannel(InetSocketAddress(21060))
|
||||
}.exceptionally { LOGGER.error("what", it); null }
|
||||
Starbound.addArchive(file)
|
||||
//Starbound.addPath(File("K:\\git\\kstarbound\\unpacked_assets"))
|
||||
|
||||
/*for (f in File("J:\\Steam\\steamapps\\workshop\\content\\211820").listFiles()!!) {
|
||||
for (f2 in f.listFiles()!!) {
|
||||
if (f2.isFile) {
|
||||
Starbound.addArchive(f2)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
LOGGER.info("Running LWJGL ${Version.getVersion()}")
|
||||
|
||||
val client = StarboundClient()
|
||||
|
||||
Starbound.initializeGame().thenApply {
|
||||
val server = IntegratedStarboundServer(client, if (storageFolder == null) File(data, "storage") else File(storageFolder!!))
|
||||
server.channels.createChannel(InetSocketAddress(21060))
|
||||
}.exceptionally { LOGGER.error("what", it); null }
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
fun main(vararg args: String) {
|
||||
val result = CommandLine(StartClientCommand()).execute(*args)
|
||||
|
||||
if (result != 0) {
|
||||
exitProcess(result)
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package ru.dbotthepony.kstarbound
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.future.await
|
||||
@ -14,6 +16,7 @@ import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.DamageKind
|
||||
import ru.dbotthepony.kstarbound.defs.Json2Function
|
||||
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
||||
@ -32,6 +35,10 @@ import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.ProjectileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorNodeDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterPaletteSwap
|
||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterPartDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
||||
@ -42,17 +49,21 @@ import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.world.SpawnType
|
||||
import ru.dbotthepony.kstarbound.item.ItemRegistry
|
||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.fromJsonTreeFast
|
||||
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Future
|
||||
import java.util.random.RandomGenerator
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object Registries {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
@ -83,6 +94,10 @@ object Registries {
|
||||
val treasureChests = Registry<TreasureChestDefinition>("treasure chest").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val monsterSkills = Registry<MonsterSkillDefinition>("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val monsterTypes = Registry<MonsterTypeDefinition>("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val spawnTypes = Registry<SpawnType>("spawn type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val monsterPalettes = Registry<MonsterPaletteSwap>("monster palette").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val behavior = Registry<BehaviorDefinition>("behavior").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val behaviorNodes = Registry<BehaviorNodeDefinition>("behavior node").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val worldObjects = Registry<ObjectDefinition>("world object").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val biomes = Registry<BiomeDefinition>("biome").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val terrainSelectors = Registry<TerrainSelectorType.Factory<*, *>>("terrain selector").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
@ -92,6 +107,55 @@ object Registries {
|
||||
val bushVariants = Registry<BushVariant.Data>("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val dungeons = Registry<DungeonDefinition>("dungeon").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val markovGenerators = Registry<MarkovTextGenerator>("markov text generator").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val damageKinds = Registry<DamageKind>("damage kind").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
|
||||
private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>()
|
||||
private val loggedMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>())
|
||||
|
||||
fun selectMonsterPart(category: String, type: String, random: RandomGenerator): MonsterPartDefinition? {
|
||||
val key = category to type
|
||||
val get = monsterParts[key]
|
||||
|
||||
if (get.isNullOrEmpty()) {
|
||||
if (loggedMisses.add(key)) {
|
||||
LOGGER.error("No such monster part combination of category '$category' and type '$type'")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return get.values.random(random, true).first
|
||||
}
|
||||
|
||||
fun getMonsterPart(category: String, type: String, name: String): MonsterPartDefinition? {
|
||||
return monsterParts[category to type]?.get(name)?.first
|
||||
}
|
||||
|
||||
private fun loadMonsterParts(files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
||||
val adapter by lazy { Starbound.gson.getAdapter(MonsterPartDefinition::class.java) }
|
||||
|
||||
return files.map { listedFile ->
|
||||
Starbound.GLOBAL_SCOPE.launch {
|
||||
try {
|
||||
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()])
|
||||
val next = AssetPathStack(listedFile.computeFullPath()) { adapter.fromJsonTreeFast(elem) }
|
||||
|
||||
Starbound.submit {
|
||||
val map = monsterParts.computeIfAbsent(next.category to next.type) { HashMap() }
|
||||
val old = map[next.name]
|
||||
|
||||
if (old != null) {
|
||||
LOGGER.error("Duplicate monster part '${next.name}' of category '${next.category}' and type '${next.type}', originating from $listedFile (old originate from ${old.second})")
|
||||
} else {
|
||||
map[next.name] = next to listedFile
|
||||
}
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Loading monster part definition file $listedFile", err)
|
||||
}
|
||||
}.asCompletableFuture()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, KOptional<Int?>> {
|
||||
return { mapper.invoke(it) to KOptional() }
|
||||
@ -122,7 +186,7 @@ object Registries {
|
||||
return files.map { listedFile ->
|
||||
Starbound.GLOBAL_SCOPE.launch {
|
||||
try {
|
||||
val elem = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()])
|
||||
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()])
|
||||
|
||||
AssetPathStack(listedFile.computeFullPath()) {
|
||||
val read = adapter.fromJsonTreeFast(elem)
|
||||
@ -130,7 +194,7 @@ object Registries {
|
||||
|
||||
after(read, listedFile)
|
||||
|
||||
registry.add {
|
||||
Starbound.submit {
|
||||
registry.add(
|
||||
key = keys.first,
|
||||
value = read,
|
||||
@ -138,7 +202,7 @@ object Registries {
|
||||
json = elem,
|
||||
file = listedFile
|
||||
)
|
||||
}
|
||||
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition file $listedFile", err); null }
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
|
||||
@ -147,10 +211,6 @@ object Registries {
|
||||
}
|
||||
}
|
||||
|
||||
fun finishLoad() {
|
||||
registriesInternal.forEach { it.finishLoad() }
|
||||
}
|
||||
|
||||
fun load(fileTree: Map<String, Collection<IStarboundFile>>, patchTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
|
||||
val tasks = ArrayList<Future<*>>()
|
||||
|
||||
@ -165,14 +225,18 @@ object Registries {
|
||||
tasks.add(loadMetaMaterials())
|
||||
tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name)))
|
||||
|
||||
tasks.addAll(loadMonsterParts(fileTree["monsterpart"] ?: listOf(), patchTree))
|
||||
|
||||
tasks.addAll(loadRegistry(worldObjects, patchTree, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
||||
tasks.addAll(loadRegistry(monsterTypes, patchTree, fileTree["monstertype"] ?: listOf(), key(MonsterTypeDefinition::type)))
|
||||
tasks.addAll(loadRegistry(monsterPalettes, patchTree, fileTree["monstercolors"] ?: listOf(), key(MonsterPaletteSwap::name)))
|
||||
tasks.addAll(loadRegistry(monsterSkills, patchTree, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
|
||||
tasks.addAll(loadRegistry(statusEffects, patchTree, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
|
||||
tasks.addAll(loadRegistry(species, patchTree, fileTree["species"] ?: listOf(), key(Species::kind)))
|
||||
tasks.addAll(loadRegistry(particles, patchTree, fileTree["particle"] ?: listOf(), { (it.kind ?: throw NullPointerException("Missing 'kind' value")) to KOptional() }))
|
||||
tasks.addAll(loadRegistry(questTemplates, patchTree, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
|
||||
tasks.addAll(loadRegistry(techs, patchTree, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
|
||||
tasks.addAll(loadRegistry(npcTypes, patchTree, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
|
||||
tasks.addAll(loadRegistry(monsterSkills, patchTree, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
|
||||
tasks.addAll(loadRegistry(biomes, patchTree, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name)))
|
||||
tasks.addAll(loadRegistry(grassVariants, patchTree, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::name)))
|
||||
tasks.addAll(loadRegistry(treeStemVariants, patchTree, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name)))
|
||||
@ -180,6 +244,10 @@ object Registries {
|
||||
tasks.addAll(loadRegistry(bushVariants, patchTree, fileTree["bush"] ?: listOf(), key(BushVariant.Data::name)))
|
||||
tasks.addAll(loadRegistry(markovGenerators, patchTree, fileTree["namesource"] ?: listOf(), key(MarkovTextGenerator::name)))
|
||||
tasks.addAll(loadRegistry(projectiles, patchTree, fileTree["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)))
|
||||
tasks.addAll(loadRegistry(behavior, patchTree, fileTree["behavior"] ?: listOf(), key(BehaviorDefinition::name)))
|
||||
tasks.addAll(loadRegistry(damageKinds, patchTree, fileTree["damage"] ?: listOf(), key(DamageKind::kind)))
|
||||
|
||||
tasks.addAll(loadCombined(behaviorNodes, fileTree["nodes"] ?: listOf(), patchTree))
|
||||
|
||||
tasks.addAll(loadCombined(jsonFunctions, fileTree["functions"] ?: listOf(), patchTree))
|
||||
tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: listOf(), patchTree))
|
||||
@ -187,6 +255,10 @@ object Registries {
|
||||
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
|
||||
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf(), patchTree) { name = it })
|
||||
|
||||
// because someone couldn't handle their mushroom vine that day, and decided to make third way of
|
||||
// declaring game data
|
||||
tasks.addAll(loadMixed(spawnTypes, fileTree["spawntypes"] ?: listOf(), patchTree, SpawnType::name))
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
@ -196,16 +268,16 @@ object Registries {
|
||||
return files.map { listedFile ->
|
||||
Starbound.GLOBAL_SCOPE.launch {
|
||||
try {
|
||||
val json = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
||||
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
||||
|
||||
for ((k, v) in json.entrySet()) {
|
||||
try {
|
||||
val value = adapter.fromJsonTreeFast(v)
|
||||
transform(value, k)
|
||||
|
||||
registry.add {
|
||||
Starbound.submit {
|
||||
registry.add(k, value, v, listedFile)
|
||||
}
|
||||
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err); null }
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err)
|
||||
}
|
||||
@ -217,13 +289,40 @@ object Registries {
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> loadMixed(registry: Registry<T>, files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>, noinline key: T.() -> String): List<Future<*>> {
|
||||
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
||||
|
||||
return files.map { listedFile ->
|
||||
Starbound.GLOBAL_SCOPE.launch {
|
||||
try {
|
||||
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonArray
|
||||
|
||||
for ((i, v) in json.withIndex()) {
|
||||
try {
|
||||
val value = adapter.fromJsonTreeFast(v)
|
||||
val getKey = key(value)
|
||||
|
||||
Starbound.submit {
|
||||
registry.add(getKey, value, v, listedFile)
|
||||
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition at name '$getKey' from file $listedFile", err); null }
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Loading ${registry.name} definition at index $i from file $listedFile", err)
|
||||
}
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
||||
}
|
||||
}.asCompletableFuture()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
||||
try {
|
||||
val json = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
||||
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
||||
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
|
||||
val factory = TerrainSelectorType.factory(json, false, type)
|
||||
|
||||
terrainSelectors.add {
|
||||
Starbound.submit {
|
||||
terrainSelectors.add(name, factory)
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
@ -268,7 +367,7 @@ object Registries {
|
||||
val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTreeFast(read)
|
||||
|
||||
for (def in read2) {
|
||||
tiles.add {
|
||||
Starbound.submit {
|
||||
tiles.add(key = "metamaterial:${def.name}", id = KOptional(def.materialId), value = TileDefinition(
|
||||
isMeta = true,
|
||||
materialId = def.materialId,
|
||||
|
@ -7,48 +7,34 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.XXHash64
|
||||
import ru.dbotthepony.kstarbound.util.limit
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import java.util.function.Supplier
|
||||
import kotlin.collections.set
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class Registry<T : Any>(val name: String) {
|
||||
class Registry<T : Any>(val name: String, val storeJson: Boolean = true) {
|
||||
private val keysInternal = HashMap<String, Impl>()
|
||||
private val idsInternal = Int2ObjectOpenHashMap<Impl>()
|
||||
private val keyRefs = HashMap<String, RefImpl>()
|
||||
private val idRefs = Int2ObjectOpenHashMap<RefImpl>()
|
||||
private val backlog = ConcurrentLinkedQueue<Runnable>()
|
||||
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
|
||||
private var hasBeenValidated = false
|
||||
private val loggedMisses = ObjectOpenHashSet<String>()
|
||||
|
||||
// it is much cheaper to queue registry additions rather than locking during high congestion
|
||||
fun add(task: Runnable) {
|
||||
backlog.add(task)
|
||||
}
|
||||
|
||||
fun finishLoad() {
|
||||
lock.write {
|
||||
var next = backlog.poll()
|
||||
|
||||
while (next != null) {
|
||||
next.run()
|
||||
next = backlog.poll()
|
||||
}
|
||||
}
|
||||
}
|
||||
// idiot-proof miss lookup. Surely, it will cause some entries to be never logged
|
||||
// if they are missing, but at least if malicious actor spams with long-ass invalid data
|
||||
// it won't explode memory usage of server
|
||||
private val loggedMisses = LongOpenHashSet()
|
||||
|
||||
val keys: Map<String, Entry<T>> = Collections.unmodifiableMap(keysInternal)
|
||||
val ids: Int2ObjectMap<out Entry<T>> = Int2ObjectMaps.unmodifiable(idsInternal)
|
||||
@ -102,8 +88,21 @@ class Registry<T : Any>(val name: String) {
|
||||
return this === other
|
||||
}
|
||||
|
||||
private val hash: Int
|
||||
|
||||
init {
|
||||
var x = key.hashCode()
|
||||
// avalanche bits using murmur3 hash
|
||||
x = x xor (x ushr 16)
|
||||
x *= -0x7a143595
|
||||
x = x xor (x ushr 13)
|
||||
x *= -0x3d4d51cb
|
||||
x = x xor (x ushr 16)
|
||||
hash = x
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return key.hashCode()
|
||||
return hash
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -122,8 +121,21 @@ class Registry<T : Any>(val name: String) {
|
||||
return this === other || other is Registry<*>.RefImpl && other.key == key && other.registry == registry
|
||||
}
|
||||
|
||||
private val hash: Int
|
||||
|
||||
init {
|
||||
var x = key.hashCode()
|
||||
// avalanche bits using murmur3 hash
|
||||
x = x xor (x ushr 16)
|
||||
x *= -0x7a143595
|
||||
x = x xor (x ushr 13)
|
||||
x *= -0x3d4d51cb
|
||||
x = x xor (x ushr 16)
|
||||
hash = x
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return key.hashCode()
|
||||
return hash
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -138,9 +150,13 @@ class Registry<T : Any>(val name: String) {
|
||||
val result = lock.read { keysInternal[index] }
|
||||
|
||||
if (result == null && hasBeenValidated) {
|
||||
val hasher = XXHash64()
|
||||
hasher.update(index.toByteArray())
|
||||
val missIndex = hasher.digestAsLong()
|
||||
|
||||
lock.write {
|
||||
if (loggedMisses.add(index)) {
|
||||
LOGGER.warn("No such $name: $index")
|
||||
if (loggedMisses.add(missIndex)) {
|
||||
LOGGER.warn("No such $name: ${index.limit()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,8 +168,15 @@ class Registry<T : Any>(val name: String) {
|
||||
val result = lock.read { idsInternal[index] }
|
||||
|
||||
if (result == null && hasBeenValidated) {
|
||||
val hasher = XXHash64()
|
||||
hasher.update(index.toByte())
|
||||
hasher.update((index ushr 8).toByte())
|
||||
hasher.update((index ushr 16).toByte())
|
||||
hasher.update((index ushr 24).toByte())
|
||||
val missIndex = hasher.digestAsLong()
|
||||
|
||||
lock.write {
|
||||
if (loggedMisses.add(index.toString())) {
|
||||
if (loggedMisses.add(missIndex)) {
|
||||
LOGGER.warn("No such $name: ID $index")
|
||||
}
|
||||
}
|
||||
@ -170,13 +193,23 @@ class Registry<T : Any>(val name: String) {
|
||||
return get(index) ?: throw NoSuchElementException("No such $name: $index")
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used inside network readers, so clients sending invalid data will get kicked
|
||||
*/
|
||||
fun refOrThrow(index: String): Ref<T> = get(index)?.ref ?: throw NoSuchElementException("No such $name: ${index.limit()}")
|
||||
|
||||
fun ref(index: String): Ref<T> = lock.write {
|
||||
keyRefs.computeIfAbsent(index, Object2ObjectFunction {
|
||||
val ref = RefImpl(Either.left(it as String))
|
||||
ref.entry = keysInternal[it]
|
||||
|
||||
if (hasBeenValidated && ref.entry == null && loggedMisses.add(it)) {
|
||||
LOGGER.warn("No such $name: $it")
|
||||
if (hasBeenValidated && ref.entry == null) {
|
||||
val hasher = XXHash64()
|
||||
hasher.update(index.toByteArray())
|
||||
|
||||
if (loggedMisses.add(hasher.digestAsLong())) {
|
||||
LOGGER.warn("No such $name: ${it.limit()}")
|
||||
}
|
||||
}
|
||||
|
||||
ref
|
||||
@ -185,13 +218,26 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used inside network readers, so clients sending invalid data will get kicked
|
||||
*/
|
||||
fun refOrThrow(index: Int): Ref<T> = get(index)?.ref ?: throw NoSuchElementException("No such $name: ID $index")
|
||||
|
||||
fun ref(index: Int): Ref<T> = lock.write {
|
||||
idRefs.computeIfAbsent(index, Int2ObjectFunction {
|
||||
val ref = RefImpl(Either.right(it))
|
||||
ref.entry = idsInternal[it]
|
||||
|
||||
if (hasBeenValidated && ref.entry == null && loggedMisses.add(it.toString())) {
|
||||
LOGGER.warn("No such $name: ID $it")
|
||||
if (hasBeenValidated && ref.entry == null) {
|
||||
val hasher = XXHash64()
|
||||
hasher.update(index.toByte())
|
||||
hasher.update((index ushr 8).toByte())
|
||||
hasher.update((index ushr 16).toByte())
|
||||
hasher.update((index ushr 24).toByte())
|
||||
|
||||
if (loggedMisses.add(hasher.digestAsLong())) {
|
||||
LOGGER.warn("No such $name: ID $it")
|
||||
}
|
||||
}
|
||||
|
||||
ref
|
||||
@ -261,7 +307,10 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
|
||||
entry.value = value
|
||||
entry.json = json
|
||||
|
||||
if (storeJson)
|
||||
entry.json = json
|
||||
|
||||
entry.file = file
|
||||
entry.isBuiltin = isBuiltin
|
||||
|
||||
|
@ -13,8 +13,10 @@ import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||
import org.classdump.luna.compiler.CompilerSettings
|
||||
@ -344,7 +346,6 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
||||
|
||||
registerTypeAdapterFactory(JsonReference.Companion)
|
||||
|
||||
registerTypeAdapter(ColorReplacements.Companion)
|
||||
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
||||
|
||||
registerTypeAdapter(RGBAColorTypeAdapter)
|
||||
@ -619,7 +620,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
||||
return files
|
||||
}
|
||||
|
||||
private fun doInitialize() {
|
||||
private suspend fun doInitialize() {
|
||||
if (!initializing && !initialized) {
|
||||
initializing = true
|
||||
} else {
|
||||
@ -665,21 +666,29 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
||||
loaded = toLoad - tasks.size
|
||||
loadingProgress = (total - tasks.size) / total
|
||||
loadingProgressText = "Loading JSON assets, $loaded / $toLoad"
|
||||
LockSupport.parkNanos(5_000_000L)
|
||||
delay(10L)
|
||||
}
|
||||
|
||||
Registries.finishLoad()
|
||||
RecipeRegistry.finishLoad()
|
||||
ItemRegistry.finishLoad()
|
||||
|
||||
Registries.validate()
|
||||
|
||||
initializing = false
|
||||
initialized = true
|
||||
|
||||
// Suggest VM to reclaim memory after game has been loaded
|
||||
System.gc()
|
||||
}
|
||||
|
||||
private var initializationFuture: CompletableFuture<*>? = null
|
||||
|
||||
fun initializeGame(): CompletableFuture<*> {
|
||||
return submit { doInitialize() }
|
||||
return supplyAsync {
|
||||
if (initializationFuture == null) {
|
||||
initializationFuture = scope.launch { doInitialize() }.asCompletableFuture()
|
||||
}
|
||||
|
||||
return@supplyAsync initializationFuture!!
|
||||
}.thenCompose { it }
|
||||
}
|
||||
|
||||
private var fontPath: File? = null
|
||||
|
@ -109,9 +109,9 @@ interface IStarboundFile : ISBFileLocator {
|
||||
val children = file.children ?: return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
|
||||
val find = children[split[splitIndex]]
|
||||
|
||||
if (find is StarboundPak.SBDirectory) {
|
||||
if (find is StarboundPak.SBDirectory || find != null && find.isDirectory) {
|
||||
file = find
|
||||
} else if (find is StarboundPak.SBFile) {
|
||||
} else if (find is StarboundPak.SBFile || find != null && find.isFile) {
|
||||
if (splitIndex + 1 != split.size) {
|
||||
return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
|
||||
}
|
||||
@ -270,10 +270,10 @@ class PhysicalFile(val real: File, override val parent: PhysicalFile? = null) :
|
||||
get() = real.isFile
|
||||
override val children: Map<String, PhysicalFile>?
|
||||
get() {
|
||||
return real.list()?.associate { it to PhysicalFile(File(it), this) }
|
||||
return real.list()?.associate { it.lowercase() to PhysicalFile(File(real, it), this) }
|
||||
}
|
||||
override val name: String
|
||||
get() = real.name
|
||||
get() = if (parent == null) "" else real.name.lowercase()
|
||||
|
||||
private val fullPatch by lazy { super.computeFullPath().sbIntern() }
|
||||
private val directory by lazy { super.computeDirectory(false).sbIntern() }
|
||||
|
@ -62,6 +62,7 @@ import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||
import ru.dbotthepony.kstarbound.world.ChunkState
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.RayDirection
|
||||
@ -78,12 +79,14 @@ import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.ForkJoinWorkerThread
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Function
|
||||
import java.util.function.IntConsumer
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.withLock
|
||||
@ -335,6 +338,33 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
||||
texture
|
||||
}
|
||||
|
||||
val busyTexture: CompletableFuture<GLTexture2D> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
Starbound.IO_EXECUTOR.supplyAsync {
|
||||
MemoryStack.stackPush().use { stack ->
|
||||
val pWidth = stack.mallocInt(1)
|
||||
val pHeight = stack.mallocInt(1)
|
||||
val pChannels = stack.mallocInt(1)
|
||||
|
||||
val readFromDisk = readInternalBytes("starbound_busy.png")
|
||||
val buff = ByteBuffer.allocateDirect(readFromDisk.size)
|
||||
buff.put(readFromDisk)
|
||||
buff.position(0)
|
||||
|
||||
val data = STBImage.stbi_load_from_memory(buff, pWidth, pHeight, pChannels, 4)
|
||||
?: throw IllegalStateException("Unable to decode starbound_busy.png")
|
||||
|
||||
(pWidth[0] to pHeight[0]) to data
|
||||
}
|
||||
}.thenApplyAsync(Function { (size, data) ->
|
||||
val (w, h) = size
|
||||
val texture = GLTexture2D(w, h, GL_RGBA8)
|
||||
|
||||
texture.upload(GL_RGBA, GL_UNSIGNED_BYTE, data)
|
||||
texture.textureMagFilter = GL_NEAREST
|
||||
texture
|
||||
}, this)
|
||||
}
|
||||
|
||||
val missingTexture by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val texture = GLTexture2D(8, 8, GL_RGB8)
|
||||
val buffer = ByteBuffer.allocateDirect(3 * 8 * 8)
|
||||
@ -621,26 +651,14 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
||||
|
||||
private var renderedLoadingScreen = false
|
||||
|
||||
private fun renderLoadingScreen() {
|
||||
performOpenGLCleanup()
|
||||
updateViewportParams()
|
||||
|
||||
clearColor = RGBAColor.BLACK
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
private fun renderBusyDots() {
|
||||
val min = viewportHeight.coerceAtMost(viewportWidth)
|
||||
val size = min * 0.02f
|
||||
|
||||
val program = programs.positionColor
|
||||
val builder = program.builder
|
||||
|
||||
uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||
fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||
|
||||
stack.clear(Matrix3f.identity())
|
||||
|
||||
program.colorMultiplier = RGBAColor.WHITE
|
||||
|
||||
builder.builder.begin(GeometryType.QUADS)
|
||||
|
||||
if (dotCountdown-- <= 0) {
|
||||
@ -664,6 +682,69 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
||||
builder.builder.centeredQuad(viewportWidth * 0.5f - size * 2f, viewportHeight * 0.5f, size, size) { color(a) }
|
||||
builder.builder.centeredQuad(viewportWidth * 0.5f + size * 2f, viewportHeight * 0.5f, size, size) { color(c) }
|
||||
|
||||
program.use()
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
}
|
||||
|
||||
private fun renderBusyLogo() {
|
||||
val min = viewportHeight.coerceAtMost(viewportWidth)
|
||||
val size = min * 0.0035f
|
||||
val width = size * 46f
|
||||
val height = size * 28f
|
||||
|
||||
val program = programs.positionTexture
|
||||
val builder = program.builder
|
||||
|
||||
program.texture0 = 0
|
||||
textures2D[0] = busyTexture.get()
|
||||
program.colorMultiplier = RGBAColor.WHITE
|
||||
|
||||
builder.builder.begin(GeometryType.QUADS)
|
||||
|
||||
val x = viewportWidth * 0.5f
|
||||
val y = viewportHeight * 0.5f
|
||||
|
||||
val index = ((System.nanoTime() / 40_000_000L) % 29L) / 29f
|
||||
|
||||
builder.builder.vertex(x - width, y - height).uv(index, 0f)
|
||||
builder.builder.vertex(x - width, y + height).uv(index, 1f)
|
||||
builder.builder.vertex(x + width, y + height).uv(index + 1f / 29f, 1f)
|
||||
builder.builder.vertex(x + width, y - height).uv(index + 1f / 29f, 0f)
|
||||
|
||||
program.use()
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
}
|
||||
|
||||
fun renderBusyIcon() {
|
||||
if (busyTexture.isDone && !busyTexture.isCompletedExceptionally) {
|
||||
renderBusyLogo()
|
||||
} else {
|
||||
renderBusyDots()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderLoadingScreen() {
|
||||
performOpenGLCleanup()
|
||||
updateViewportParams()
|
||||
|
||||
clearColor = RGBAColor.BLACK
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||
fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||
|
||||
stack.clear(Matrix3f.identity())
|
||||
|
||||
renderBusyIcon()
|
||||
|
||||
val program = programs.positionColor
|
||||
val builder = program.builder
|
||||
|
||||
program.colorMultiplier = RGBAColor.WHITE
|
||||
|
||||
builder.builder.begin(GeometryType.QUADS)
|
||||
builder.builder.quad(0f, viewportHeight - 20f, viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight.toFloat()) { color(RGBAColor.GREEN) }
|
||||
|
||||
GLFW.glfwSetWindowTitle(window, "KStarbound: ${Starbound.loadingProgressText}")
|
||||
@ -671,7 +752,6 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
||||
|
||||
val runtime = Runtime.getRuntime()
|
||||
|
||||
//if (runtime.maxMemory() <= 4L * 1024L * 1024L * 1024L) {
|
||||
builder.builder.centeredQuad(viewportWidth * 0.5f, viewportHeight * 0.1f, viewportWidth * 0.7f, 2f) { color(RGBAColor.WHITE) }
|
||||
builder.builder.centeredQuad(viewportWidth * 0.5f, viewportHeight * 0.1f + 20f, viewportWidth * 0.7f, 2f) { color(RGBAColor.WHITE) }
|
||||
builder.builder.centeredQuad(viewportWidth * (0.5f - 0.35f), viewportHeight * 0.1f + 10f, 2f, 20f) { color(RGBAColor.WHITE) }
|
||||
@ -687,7 +767,6 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
||||
leftEdge + viewportWidth * 0.7f * ((runtime.totalMemory() - runtime.freeMemory()) / runtime.maxMemory().toDouble()).toFloat(),
|
||||
viewportHeight * 0.1f + 19f
|
||||
) { color(RGBAColor(29, 140, 160)) }
|
||||
//}
|
||||
|
||||
if (fontInitialized) {
|
||||
drawPerformanceBasic(true)
|
||||
|
@ -1,31 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.client.network.packets
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class SpawnWorldObjectPacket(val uuid: UUID, val data: JsonObject) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUUID(), stream.readJsonObject())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeUUID(uuid)
|
||||
stream.writeJsonObject(data)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
connection.client.mailbox.execute {
|
||||
val world = connection.client.world ?: return@execute
|
||||
val obj = WorldObject.fromJson(data)
|
||||
//obj.uuid = uuid
|
||||
obj.joinWorld(world)
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class IdMap<T : Any>(val min: Int = 0, val max: Int = Int.MAX_VALUE, private val
|
||||
return nextIndex
|
||||
}
|
||||
|
||||
fun add(value: T): Int {
|
||||
fun nextFreeIndex(): Int {
|
||||
var i = 0
|
||||
var index = next()
|
||||
|
||||
@ -41,6 +41,11 @@ class IdMap<T : Any>(val min: Int = 0, val max: Int = Int.MAX_VALUE, private val
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
fun add(value: T): Int {
|
||||
val index = nextFreeIndex()
|
||||
map[index] = value
|
||||
return index
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class ActorMovementModifiers(
|
||||
val groundMovementModifier: Double = 0.0,
|
||||
val liquidMovementModifier: Double = 0.0,
|
||||
val speedModifier: Double = 0.0,
|
||||
val airJumpModifier: Double = 0.0,
|
||||
val liquidJumpModifier: Double = 0.0,
|
||||
val runningSuppressed: Boolean = false,
|
||||
val jumpingSuppressed: Boolean = false,
|
||||
val movementSuppressed: Boolean = false,
|
||||
val facingSuppressed: Boolean = false,
|
||||
) {
|
||||
fun combine(other: ActorMovementModifiers): ActorMovementModifiers {
|
||||
return ActorMovementModifiers(
|
||||
groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
|
||||
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
|
||||
speedModifier = speedModifier * other.speedModifier,
|
||||
airJumpModifier = airJumpModifier * other.airJumpModifier,
|
||||
liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier,
|
||||
runningSuppressed = runningSuppressed || other.runningSuppressed,
|
||||
jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed,
|
||||
movementSuppressed = movementSuppressed || other.movementSuppressed,
|
||||
facingSuppressed = facingSuppressed || other.facingSuppressed,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
|
||||
@JsonAdapter(ColorReplacements.Adapter::class)
|
||||
class ColorReplacements private constructor(private val mapping: Int2IntOpenHashMap) {
|
||||
constructor(mapping: Map<Int, Int>) : this(Int2IntOpenHashMap(mapping))
|
||||
|
||||
@ -15,9 +19,11 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
||||
return mapping.getOrDefault(color, color)
|
||||
}
|
||||
|
||||
companion object : TypeAdapter<ColorReplacements>() {
|
||||
val EMPTY = ColorReplacements(Int2IntOpenHashMap())
|
||||
fun toImageOperator(): String {
|
||||
return "replace;${mapping.int2IntEntrySet().joinToString(";") { "${RGBAColor.rgb(it.intKey).toHexStringRGB().substring(1)}=${RGBAColor.rgb(it.intValue).toHexStringRGB().substring(1)}" }}"
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<ColorReplacements>() {
|
||||
override fun write(out: JsonWriter, value: ColorReplacements?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
@ -25,8 +31,8 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
||||
out.beginObject()
|
||||
|
||||
for ((k, v) in value.mapping) {
|
||||
out.name(k.toString(16))
|
||||
out.value(v.toString(16))
|
||||
out.name(RGBAColor.rgb(k).toHexStringRGB())
|
||||
out.value(RGBAColor.rgb(v).toHexStringRGB())
|
||||
}
|
||||
|
||||
out.endObject()
|
||||
@ -40,7 +46,7 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
||||
if (`in`.nextString() != "")
|
||||
throw JsonSyntaxException("Invalid color replacement definition near ${`in`.path}")
|
||||
|
||||
return ColorReplacements.EMPTY
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
val mapping = Int2IntOpenHashMap()
|
||||
@ -48,10 +54,20 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
||||
`in`.beginObject()
|
||||
|
||||
while (`in`.peek() != JsonToken.END_OBJECT) {
|
||||
val k = `in`.nextName()
|
||||
val v = `in`.nextString()
|
||||
val kGet = `in`.nextName()
|
||||
val vGet = `in`.nextString()
|
||||
val k = RGBAColor.fromHexStringRGB(kGet)
|
||||
val v = RGBAColor.fromHexStringRGB(vGet)
|
||||
|
||||
mapping[k.toInt(16)] = v.toInt(16)
|
||||
if (k == null) {
|
||||
throw JsonSyntaxException("Not a valid Hex color: $kGet")
|
||||
}
|
||||
|
||||
if (v == null) {
|
||||
throw JsonSyntaxException("Not a valid Hex color: $vGet")
|
||||
}
|
||||
|
||||
mapping[k.toRGBA()] = v.toRGBA()
|
||||
}
|
||||
|
||||
`in`.endObject()
|
||||
@ -59,4 +75,8 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
||||
return ColorReplacements(mapping)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = ColorReplacements(Int2IntOpenHashMap())
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
@ -81,6 +83,13 @@ enum class DamageType(override val jsonName: String) : IStringSerializable {
|
||||
STATUS("Environment");
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class DamageKind(
|
||||
val kind: String,
|
||||
val elementalType: String = "default",
|
||||
val effects: JsonObject = JsonObject()
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort())
|
||||
@ -189,10 +198,10 @@ data class DamageData(
|
||||
val hitType: HitType,
|
||||
val damageType: DamageType,
|
||||
val damage: Double,
|
||||
val knockback: Vector2d,
|
||||
val knockbackMomentum: Vector2d,
|
||||
val sourceEntityId: Int,
|
||||
val inflictorEntityId: Int = 0,
|
||||
val kind: String,
|
||||
val damageSourceKind: String,
|
||||
val statusEffects: Collection<EphemeralStatusEffect>,
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
|
||||
@ -213,9 +222,9 @@ data class DamageData(
|
||||
stream.writeEnumStupid(hitType.ordinal, isLegacy)
|
||||
stream.writeByte(damageType.ordinal)
|
||||
stream.writeDouble(damage, isLegacy)
|
||||
stream.writeStruct2d(knockback, isLegacy)
|
||||
stream.writeStruct2d(knockbackMomentum, isLegacy)
|
||||
stream.writeInt(sourceEntityId)
|
||||
stream.writeBinaryString(kind)
|
||||
stream.writeBinaryString(damageSourceKind)
|
||||
stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
|
||||
}
|
||||
}
|
||||
@ -363,3 +372,5 @@ data class DamageSource(
|
||||
val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write)
|
||||
}
|
||||
}
|
||||
|
||||
data class ElementalDamageType(val resistanceStat: String, val damageNumberParticles: ImmutableMap<HitType, String>)
|
||||
|
@ -1,16 +1,22 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterVariant
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectType
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ProjectileEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.ContainerObject
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantPieceEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
@ -36,7 +42,19 @@ enum class EntityType(override val jsonName: String, val storeName: String, val
|
||||
}
|
||||
|
||||
override fun fromStorage(data: JsonObject): AbstractEntity {
|
||||
return WorldObject.fromJson(data)
|
||||
val prototype = Registries.worldObjects[data["name"]?.asString ?: throw IllegalArgumentException("Missing object name")] ?: throw IllegalArgumentException("No such object defined for '${data["name"]}'")
|
||||
|
||||
val result = when (prototype.value.objectType) {
|
||||
ObjectType.OBJECT -> WorldObject(prototype)
|
||||
ObjectType.LOUNGEABLE -> LoungeableObject(prototype)
|
||||
ObjectType.CONTAINER -> ContainerObject(prototype)
|
||||
ObjectType.FARMABLE -> TODO("ObjectType.FARMABLE")
|
||||
ObjectType.TELEPORTER -> TODO("ObjectType.TELEPORTER")
|
||||
ObjectType.PHYSICS -> TODO("ObjectType.PHYSICS")
|
||||
}
|
||||
|
||||
result.deserialize(data)
|
||||
return result
|
||||
}
|
||||
},
|
||||
|
||||
@ -92,11 +110,24 @@ enum class EntityType(override val jsonName: String, val storeName: String, val
|
||||
|
||||
MONSTER("monster", "MonsterEntity", false, false) {
|
||||
override fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity {
|
||||
TODO("MONSTER")
|
||||
return MonsterEntity(MonsterVariant.read(stream, isLegacy))
|
||||
}
|
||||
|
||||
override fun fromStorage(data: JsonObject): AbstractEntity {
|
||||
TODO("MONSTER")
|
||||
val variantJson = data["monsterVariant"].asJsonObject
|
||||
|
||||
val variant = if (variantJson.size() == 3) {
|
||||
val type = variantJson["type"].asString
|
||||
val seed = variantJson["seed"].asLong
|
||||
val uniqueParameters = variantJson["uniqueParameters"].asJsonObject
|
||||
Registries.monsterTypes.getOrThrow(type).value.create(seed, uniqueParameters)
|
||||
} else {
|
||||
Starbound.gson.fromJsonFast(variantJson, MonsterVariant::class.java)
|
||||
}
|
||||
|
||||
val entity = MonsterEntity(variant)
|
||||
entity.deserialize(data)
|
||||
return entity
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -6,39 +6,41 @@ import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.io.readNullableDouble
|
||||
import ru.dbotthepony.kstarbound.io.writeNullable
|
||||
import ru.dbotthepony.kstarbound.io.writeNullableDouble
|
||||
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
@JsonAdapter(EphemeralStatusEffect.Adapter::class)
|
||||
data class EphemeralStatusEffect(val effect: String, val duration: Double? = null) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInternedString(), stream.readNullableDouble())
|
||||
data class EphemeralStatusEffect(val effect: Registry.Ref<StatusEffectDefinition>, val duration: Double? = null) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(Registries.statusEffects.refOrThrow(stream.readBinaryString()), stream.readNullableDouble())
|
||||
constructor(effect: Registry.Entry<StatusEffectDefinition>, duration: Double? = null) : this(effect.ref, duration)
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<EphemeralStatusEffect>() {
|
||||
private val factory = FactoryAdapter.createFor(EphemeralStatusEffect::class, gson = gson)
|
||||
|
||||
override fun write(out: JsonWriter, value: EphemeralStatusEffect) {
|
||||
if (value.duration == null)
|
||||
out.value(value.effect)
|
||||
out.value(value.effect.key.left())
|
||||
else
|
||||
factory.write(out, value)
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): EphemeralStatusEffect {
|
||||
if (`in`.peek() == JsonToken.STRING)
|
||||
return EphemeralStatusEffect(`in`.nextString())
|
||||
return EphemeralStatusEffect(Registries.statusEffects.ref(`in`.nextString()))
|
||||
else
|
||||
return factory.read(`in`)
|
||||
}
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeBinaryString(effect)
|
||||
stream.writeBinaryString(effect.key.left())
|
||||
stream.writeNullableDouble(duration, isLegacy)
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ sealed class SpawnTarget {
|
||||
tickets.addAll(world.permanentChunkTicket(testRect).await())
|
||||
tickets.forEach { it.chunk.await() }
|
||||
|
||||
if (!world.anyCellSatisfies(testRect) { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision })
|
||||
if (!world.chunkMap.anyCellSatisfies(testRect) { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision })
|
||||
return testPos
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import java.util.UUID
|
||||
@JsonAdapter(WorldID.Adapter::class)
|
||||
sealed class WorldID {
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
val isLimbo: Boolean get() = this is Limbo
|
||||
val isLimbo: Boolean get() = this === Limbo
|
||||
|
||||
object Limbo : WorldID() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
package ru.dbotthepony.kstarbound.defs.actor
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
class ActorMovementModifiers(
|
||||
data class ActorMovementModifiers(
|
||||
val groundMovementModifier: Double = 1.0,
|
||||
val liquidMovementModifier: Double = 1.0,
|
||||
val speedModifier: Double = 1.0,
|
||||
@ -11,10 +11,10 @@ class ActorMovementModifiers(
|
||||
val liquidJumpModifier: Double = 1.0,
|
||||
val runningSuppressed: Boolean = false,
|
||||
val jumpingSuppressed: Boolean = false,
|
||||
val facingSuppressed: Boolean = false,
|
||||
val movementSuppressed: Boolean = false,
|
||||
val facingSuppressed: Boolean = false,
|
||||
) {
|
||||
fun combine(other: ActorMovementModifiers): ActorMovementModifiers {
|
||||
fun merge(other: ActorMovementModifiers): ActorMovementModifiers {
|
||||
return ActorMovementModifiers(
|
||||
groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
|
||||
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
|
||||
@ -23,8 +23,8 @@ class ActorMovementModifiers(
|
||||
liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier,
|
||||
runningSuppressed = runningSuppressed || other.runningSuppressed,
|
||||
jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed,
|
||||
facingSuppressed = facingSuppressed || other.facingSuppressed,
|
||||
movementSuppressed = movementSuppressed || other.movementSuppressed,
|
||||
facingSuppressed = facingSuppressed || other.facingSuppressed,
|
||||
)
|
||||
}
|
||||
|
@ -10,19 +10,19 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
@JsonFactory
|
||||
data class StatusControllerConfig(
|
||||
val statusProperties: JsonObject = JsonObject(),
|
||||
val minimumLiquidStatusEffectPercentage: Double,
|
||||
val appliesEnvironmentStatusEffects: Boolean,
|
||||
val appliesWeatherStatusEffects: Boolean,
|
||||
val minimumLiquidStatusEffectPercentage: Double = 0.5,
|
||||
val appliesEnvironmentStatusEffects: Boolean = true,
|
||||
val appliesWeatherStatusEffects: Boolean = true,
|
||||
val environmentStatusEffectUpdateTimer: Double = 0.15,
|
||||
val primaryAnimationConfig: AssetPath? = null,
|
||||
val primaryScriptSources: ImmutableList<AssetPath> = ImmutableList.of(),
|
||||
val primaryScriptDelta: Int = 1,
|
||||
val primaryScriptDelta: Double = 1.0,
|
||||
val keepDamageNotificationSteps: Int = 120,
|
||||
val stats: ImmutableMap<String, Stat> = ImmutableMap.of(),
|
||||
val resources: ImmutableMap<String, Resource> = ImmutableMap.of(),
|
||||
) {
|
||||
init {
|
||||
require(primaryScriptDelta >= 1) { "Non-positive primaryScriptDelta: $primaryScriptDelta" }
|
||||
require(primaryScriptDelta > 0.0) { "Non-positive primaryScriptDelta: $primaryScriptDelta" }
|
||||
require(keepDamageNotificationSteps >= 1) { "Non-positive keepDamageNotificationSteps: $keepDamageNotificationSteps" }
|
||||
}
|
||||
|
||||
@ -40,4 +40,8 @@ data class StatusControllerConfig(
|
||||
val initialValue: Double? = null,
|
||||
val initialPercentage: Double? = null,
|
||||
)
|
||||
|
||||
companion object {
|
||||
val EMPTY = StatusControllerConfig()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor
|
||||
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
// stat modifier or named status effect
|
||||
typealias PersistentStatusEffect = Either<StatModifier, Registry.Ref<StatusEffectDefinition>>
|
||||
|
||||
// uint8_t
|
||||
enum class Gender(override val jsonName: String) : IStringSerializable {
|
||||
MALE("Male"), FEMALE("Female");
|
||||
|
@ -0,0 +1,22 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class BehaviorDefinition(
|
||||
val name: String,
|
||||
val parameters: ImmutableMap<String, JsonElement> = ImmutableMap.of(),
|
||||
val scripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||
val root: JsonObject,
|
||||
) {
|
||||
val mappedParameters: ImmutableMap<String, NodeParameterValue> = parameters.entries
|
||||
.stream()
|
||||
.map { it.key to NodeParameterValue(null, it.value) }
|
||||
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
/**
|
||||
* Reflects node on behavior graph.
|
||||
*
|
||||
* Behavior graph is not what most would expect, since this is code flow graph
|
||||
* (nodes represent Lua functions), and not decision tree.
|
||||
*/
|
||||
@JsonFactory
|
||||
data class BehaviorNodeDefinition(
|
||||
/**
|
||||
* actually should be called "parameters"
|
||||
*/
|
||||
val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(),
|
||||
val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(),
|
||||
)
|
@ -0,0 +1,20 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.DynamicNode
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.ParallelNode
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.RandomizeNode
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.SequenceNode
|
||||
|
||||
enum class CompositeNodeType(override val jsonName: String, val factory: (Map<String, NodeParameter>, ImmutableList<AbstractBehaviorNode>) -> AbstractBehaviorNode) : IStringSerializable {
|
||||
SEQUENCE("Sequence", { _, c -> SequenceNode(c) }),
|
||||
// in original engine, selector and sequence nodes are identical code-wise
|
||||
SELECTOR("Selector", { _, c -> SequenceNode(c) }),
|
||||
PARALLEL("Parallel", { p, c -> ParallelNode(p, c) }),
|
||||
DYNAMIC("Dynamic", { _, c -> DynamicNode(c) }),
|
||||
RANDOMIZE("Randomize", { _, c -> RandomizeNode(c) });
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kstarbound.json.popObject
|
||||
import ru.dbotthepony.kstarbound.util.asStringOrNull
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.NodeParameterType
|
||||
|
||||
@JsonAdapter(NodeOutput.Adapter::class)
|
||||
data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) {
|
||||
class Adapter : TypeAdapter<NodeOutput>() {
|
||||
override fun write(out: JsonWriter, value: NodeOutput) {
|
||||
out.beginObject()
|
||||
out.name("type")
|
||||
out.value(value.type.jsonName)
|
||||
|
||||
if (value.key != null) {
|
||||
out.name("key")
|
||||
out.value(value.key)
|
||||
}
|
||||
|
||||
out.name("ephemeral")
|
||||
out.value(value.ephemeral)
|
||||
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): NodeOutput {
|
||||
val json = `in`.popObject()
|
||||
|
||||
return NodeOutput(
|
||||
NodeParameterType.entries.valueOf(json["type"].asString),
|
||||
json["key"]?.asStringOrNull,
|
||||
json.get("ephemeral", false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kstarbound.json.popObject
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.NodeParameterType
|
||||
|
||||
@JsonAdapter(NodeParameter.Adapter::class)
|
||||
data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) {
|
||||
override fun toString(): String {
|
||||
return "[$type as $value]"
|
||||
}
|
||||
|
||||
class Adapter : TypeAdapter<NodeParameter>() {
|
||||
override fun write(out: JsonWriter, value: NodeParameter) {
|
||||
out.beginObject()
|
||||
|
||||
out.name("type")
|
||||
out.value(value.type.jsonName)
|
||||
|
||||
if (value.value.key != null) {
|
||||
out.name("key")
|
||||
out.value(value.value.key)
|
||||
} else {
|
||||
out.name("value")
|
||||
out.value(value.value.value)
|
||||
}
|
||||
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): NodeParameter {
|
||||
val json = `in`.popObject()
|
||||
val type = NodeParameterType.entries.valueOf(json["type"].asString)
|
||||
|
||||
if ("key" in json) {
|
||||
return NodeParameter(type, NodeParameterValue(json["key"].asString, null))
|
||||
} else {
|
||||
return NodeParameter(type, NodeParameterValue(null, json["value"] ?: JsonNull.INSTANCE))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.behavior
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kstarbound.json.popObject
|
||||
|
||||
/**
|
||||
* [key] - references earlier value
|
||||
*
|
||||
* [value] - carries a value
|
||||
*/
|
||||
@JsonAdapter(NodeParameterValue.Adapter::class)
|
||||
data class NodeParameterValue(val key: String?, val value: JsonElement?) {
|
||||
override fun toString(): String {
|
||||
if (key != null)
|
||||
return "key=$key"
|
||||
else
|
||||
return "value=$value"
|
||||
}
|
||||
|
||||
class Adapter : TypeAdapter<NodeParameterValue>() {
|
||||
override fun write(out: JsonWriter, value: NodeParameterValue) {
|
||||
if (value.key != null) {
|
||||
out.name("key")
|
||||
out.value(value.key)
|
||||
} else {
|
||||
out.name("value")
|
||||
out.value(value.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): NodeParameterValue {
|
||||
val json = `in`.popObject()
|
||||
|
||||
if ("key" in json) {
|
||||
return NodeParameterValue(json["key"].asString, null)
|
||||
} else {
|
||||
return NodeParameterValue(null, json["value"] ?: JsonNull.INSTANCE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ import ru.dbotthepony.kstarbound.lua.StateMachine
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import java.io.DataInputStream
|
||||
@ -198,7 +199,7 @@ data class ItemDescriptor(
|
||||
}
|
||||
|
||||
return allocator.newTable(0, 3).also {
|
||||
it.rawset("name", name)
|
||||
it.rawset("name", name.toByteString())
|
||||
it.rawset("count", count)
|
||||
it.rawset("parameters", allocator.from(parameters))
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.monster
|
||||
|
||||
data class ActionDefinition(
|
||||
val name: String, // ссылается на .nodes?
|
||||
val cooldown: Double = -1.0,
|
||||
// val parameters
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package ru.dbotthepony.kstarbound.defs.monster
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.defs.ColorReplacements
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
class MonsterPaletteSwap(
|
||||
val name: String,
|
||||
val swaps: ImmutableList<ColorReplacements>,
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.defs.monster
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class MonsterPartDefinition(
|
||||
val name: String,
|
||||
val category: String,
|
||||
val type: String,
|
||||
|
||||
// key -> image
|
||||
val frames: ImmutableMap<String, AssetPath>,
|
||||
val parameters: JsonObject = JsonObject(),
|
||||
)
|
@ -1,15 +1,15 @@
|
||||
package ru.dbotthepony.kstarbound.defs.monster
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonElement
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class MonsterSkillDefinition(
|
||||
val name: String,
|
||||
val label: String? = null,
|
||||
val image: SpriteReference? = null,
|
||||
val config: ImmutableMap<String, JsonElement> = ImmutableMap.of(),
|
||||
val animationParameters: ImmutableMap<String, JsonElement> = ImmutableMap.of(),
|
||||
val label: String,
|
||||
val image: String,
|
||||
|
||||
val config: JsonObject = JsonObject(),
|
||||
val parameters: JsonObject = JsonObject(),
|
||||
val animationParameters: JsonObject = JsonObject(),
|
||||
)
|
||||
|
@ -3,34 +3,285 @@ package ru.dbotthepony.kstarbound.defs.monster
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import ru.dbotthepony.kommons.gson.JsonArrayCollector
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.getArray
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.IScriptable
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.util.random.MWCRandom
|
||||
import ru.dbotthepony.kstarbound.util.random.nextUInt
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
private val specialMerge = ImmutableSet.of("scripts", "skills", "specialSkills", "baseSkills")
|
||||
|
||||
private fun mergeFinalParameters(params: List<JsonElement>): JsonObject {
|
||||
val parameters = JsonObject()
|
||||
|
||||
for (baseParameters in params) {
|
||||
if (baseParameters !is JsonObject)
|
||||
continue
|
||||
|
||||
for ((k, v) in baseParameters.entrySet()) {
|
||||
// Hard-coded merge for scripts and skills parameters, otherwise merge.
|
||||
if (k in specialMerge) {
|
||||
v as JsonArray
|
||||
val v2 = parameters[k]
|
||||
|
||||
if (v2 !is JsonArray) {
|
||||
parameters[k] = v.deepCopy()
|
||||
} else {
|
||||
val result = JsonArray(v.size() + v2.size())
|
||||
result.addAll(v2)
|
||||
result.addAll(v)
|
||||
parameters[k] = result
|
||||
}
|
||||
} else {
|
||||
parameters[k] = mergeJson((parameters[k] ?: JsonNull.INSTANCE).deepCopy(), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class MonsterTypeDefinition(
|
||||
val type: String,
|
||||
@JsonFlat
|
||||
val desc: IThingWithDescription,
|
||||
val categories: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val parts: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val shortdescription: String? = null,
|
||||
val description: String? = null,
|
||||
val categories: ImmutableSet<String>,
|
||||
val parts: ImmutableSet<String>,
|
||||
val animation: AssetReference<AnimationDefinition>,
|
||||
// [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ],
|
||||
// "dropPools" : [ "smallRobotTreasure" ],
|
||||
val dropPools: Either<ImmutableList<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>>, ImmutableList<Registry.Ref<TreasurePoolDefinition>>>,
|
||||
val baseParameters: BaseParameters
|
||||
) : IThingWithDescription by desc {
|
||||
@JsonFactory
|
||||
data class BaseParameters(
|
||||
val movementSettings: ActorMovementParameters? = null,
|
||||
@JsonFlat
|
||||
val script: IScriptable,
|
||||
) : IScriptable by script
|
||||
val colors: String = "default",
|
||||
val reversed: Boolean = false,
|
||||
val dropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
|
||||
val baseParameters: JsonElement = JsonNull.INSTANCE,
|
||||
|
||||
@Deprecated("Raw property, use processed one", replaceWith = ReplaceWith("this.paramsOverrides"))
|
||||
val partParameters: JsonElement = JsonNull.INSTANCE,
|
||||
|
||||
@Deprecated("Raw property, use processed one", replaceWith = ReplaceWith("this.paramsDesc"))
|
||||
val partParameterDescription: JsonElement = JsonObject()
|
||||
) {
|
||||
val paramsDesc: CompletableFuture<JsonObject>
|
||||
val paramsOverrides: CompletableFuture<JsonObject>
|
||||
|
||||
init {
|
||||
if (partParameters.isJsonNull) {
|
||||
// outdated monsters still have partParameterDescription defined
|
||||
// directly in the
|
||||
// .monstertype file
|
||||
paramsDesc = CompletableFuture.completedFuture(partParameterDescription.asJsonObject)
|
||||
paramsOverrides = CompletableFuture.completedFuture(JsonObject())
|
||||
} else {
|
||||
// for updated monsters, use the partParameterDescription from the
|
||||
// .partparams file
|
||||
val json = Starbound.loadJsonAsset(partParameters, AssetPathStack.lastFolder())
|
||||
|
||||
paramsDesc = json.thenApply {
|
||||
it.asJsonObject.get("partParameterDescription").asJsonObject
|
||||
}
|
||||
|
||||
paramsOverrides = json.thenApply {
|
||||
it.asJsonObject.get("partParameters").asJsonObject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun create(seed: Long, uniqueParameters: JsonObject): MonsterVariant {
|
||||
// MWCRandom since this MUST product exactly the same result
|
||||
// between legacy client and new server (or new client and legacy server)
|
||||
// Thanks Chuckleclusterfuck
|
||||
val random = MWCRandom(seed.toULong(), 256, 32)
|
||||
|
||||
val copyParameters = uniqueParameters.deepCopy()
|
||||
|
||||
// select a list of monster parts
|
||||
val monsterParts = ArrayList<MonsterPartDefinition>()
|
||||
val categoryName = categories.random(random, true)
|
||||
var selectedParts = uniqueParameters["selectedParts"]
|
||||
|
||||
// key -> image
|
||||
val animatorPartTags = HashMap<String, String>()
|
||||
|
||||
if (selectedParts !is JsonObject) {
|
||||
selectedParts = JsonObject()
|
||||
uniqueParameters["selectedParts"] = selectedParts
|
||||
}
|
||||
|
||||
for (partTypeName in parts) {
|
||||
val randPart = Registries.selectMonsterPart(categoryName, partTypeName, random)
|
||||
val selectedPart = selectedParts[partTypeName]?.asString
|
||||
|
||||
if (selectedPart != null) {
|
||||
val get = Registries.getMonsterPart(categoryName, partTypeName, selectedPart)
|
||||
|
||||
if (get != null) {
|
||||
monsterParts.add(get)
|
||||
}
|
||||
} else if (randPart != null) {
|
||||
monsterParts.add(randPart)
|
||||
|
||||
// cheat the system; while this will surely poison uniqueParameters, at least
|
||||
// we will make sure selected parts are chosen deterministically across sessions / sides
|
||||
// However, this hack will only work if we are server in both meanings;
|
||||
// e.g. we are fully in charge of created monster. If monster is spawned by player,
|
||||
// then, well, it sucks.
|
||||
selectedParts[partTypeName] = randPart.name
|
||||
}
|
||||
}
|
||||
|
||||
for (partConfig in monsterParts) {
|
||||
for ((k, v) in partConfig.frames) {
|
||||
animatorPartTags[k] = v.fullPath
|
||||
}
|
||||
}
|
||||
|
||||
val partParameterList = ArrayList<JsonObject>()
|
||||
|
||||
for (partConfig in monsterParts) {
|
||||
partParameterList.add(partConfig.parameters)
|
||||
|
||||
// Include part parameter overrides
|
||||
if (partConfig.name in paramsOverrides.get()) {
|
||||
partParameterList.add(paramsOverrides.get()[partConfig.name].asJsonObject)
|
||||
}
|
||||
}
|
||||
|
||||
// merge part parameters and unique parameters into base parameters
|
||||
var parameters = JsonObject()
|
||||
|
||||
// First assign all the defaults.
|
||||
for ((k, v) in paramsDesc.get().entrySet()) {
|
||||
parameters[k] = v.asJsonArray.get(1).deepCopy()
|
||||
}
|
||||
|
||||
// Then go through parameter list and merge based on the merge rules.
|
||||
for (applyParams in partParameterList) {
|
||||
for ((k, v) in applyParams.entrySet()) {
|
||||
val mergeMethod = paramsDesc.get()[k]?.asJsonArray?.get(0)?.asString ?: "override"
|
||||
var value = parameters[k] ?: JsonNull.INSTANCE
|
||||
|
||||
when (mergeMethod.lowercase()) {
|
||||
"add" -> value = JsonPrimitive(value.asDouble + v.asDouble)
|
||||
"sub" -> value = JsonPrimitive(value.asDouble - v.asDouble)
|
||||
"multiply" -> value = JsonPrimitive(value.asDouble * v.asDouble)
|
||||
"divide" -> value = JsonPrimitive(value.asDouble / v.asDouble)
|
||||
"inverse" -> value = JsonPrimitive(v.asDouble / value.asDouble)
|
||||
"min" -> value = JsonPrimitive(min(value.asDouble, v.asDouble))
|
||||
"max" -> value = JsonPrimitive(max(value.asDouble, v.asDouble))
|
||||
"pow" -> value = JsonPrimitive(value.asDouble.pow(v.asDouble))
|
||||
"invpow" -> value = JsonPrimitive(v.asDouble.pow(value.asDouble))
|
||||
|
||||
"override" -> {
|
||||
if (!v.isJsonNull) // why does original engine check this?
|
||||
value = v
|
||||
}
|
||||
|
||||
"merge" -> {
|
||||
// "merge" means to either merge maps, or *append* lists together
|
||||
if (!v.isJsonNull) {
|
||||
if (value.isJsonNull) {
|
||||
value = v.deepCopy()
|
||||
} else if (value::class != v::class) {
|
||||
value = v.deepCopy()
|
||||
} else {
|
||||
if (v is JsonArray) {
|
||||
value = value.asJsonArray.also { it.addAll(v) }
|
||||
} else if (v is JsonObject) {
|
||||
value = mergeJson(value, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameters[k] = value
|
||||
}
|
||||
}
|
||||
|
||||
parameters = mergeFinalParameters(listOf(baseParameters, parameters))
|
||||
mergeJson(parameters, uniqueParameters)
|
||||
|
||||
var animationConfig = animation.json.get() ?: JsonNull.INSTANCE
|
||||
val skillNames = ArrayList<String>()
|
||||
|
||||
if ("baseSkills" in parameters || "specialSkills" in parameters) {
|
||||
val skillCount = parameters.get("skillCount", 2)
|
||||
|
||||
val baseSkillNames = parameters.getArray("baseSkills").deepCopy()
|
||||
val specialSkillNames = parameters.getArray("specialSkills").deepCopy()
|
||||
|
||||
// First, pick from base skills
|
||||
while (baseSkillNames.size() > 0 && skillNames.size < skillCount) {
|
||||
val pick = random.nextUInt(baseSkillNames.size().toULong() - 1UL).toInt()
|
||||
skillNames.add(baseSkillNames.remove(pick).asString)
|
||||
}
|
||||
|
||||
// ...then fill in from special skills as needed
|
||||
while (specialSkillNames.size() > 0 && skillNames.size < skillCount) {
|
||||
val pick = random.nextUInt(specialSkillNames.size().toULong() - 1UL).toInt()
|
||||
skillNames.add(specialSkillNames.remove(pick).asString)
|
||||
}
|
||||
} else if ("skills" in parameters) {
|
||||
val availableSkillNames = parameters.getArray("skills").deepCopy()
|
||||
val skillCount = min(parameters.get("skillCount", 2), availableSkillNames.size())
|
||||
|
||||
while (skillNames.size < skillCount) {
|
||||
val pick = random.nextUInt(availableSkillNames.size().toULong() - 1UL).toInt()
|
||||
skillNames.add(availableSkillNames.remove(pick).asString)
|
||||
}
|
||||
}
|
||||
|
||||
if (skillNames.isNotEmpty()) {
|
||||
animationConfig = animationConfig.deepCopy()
|
||||
val allParameters = ArrayList<JsonElement>()
|
||||
allParameters.add(parameters)
|
||||
|
||||
for (skillName in skillNames) {
|
||||
val skill = Registries.monsterSkills[skillName] ?: continue
|
||||
allParameters.add(skill.value.parameters)
|
||||
animationConfig = mergeJson(animationConfig, skill.value.animationParameters)
|
||||
}
|
||||
|
||||
// Need to override the final list of skills, instead of merging the lists
|
||||
parameters = mergeFinalParameters(allParameters)
|
||||
parameters["skills"] = skillNames.stream().map { JsonPrimitive(it) }.collect(JsonArrayCollector)
|
||||
}
|
||||
|
||||
val variant = MonsterVariant(
|
||||
type = type,
|
||||
description = description,
|
||||
shortDescription = shortdescription,
|
||||
seed = seed,
|
||||
uniqueParameters = copyParameters,
|
||||
animationConfig = Starbound.gson.fromJsonFast(animationConfig, AnimationDefinition::class.java),
|
||||
reversed = reversed,
|
||||
animatorPartTags = ImmutableMap.copyOf(animatorPartTags),
|
||||
parameters = parameters,
|
||||
dropPools = dropPools,
|
||||
)
|
||||
|
||||
return variant
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,145 @@
|
||||
package ru.dbotthepony.kstarbound.defs.monster
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.ClientEntityMode
|
||||
import ru.dbotthepony.kstarbound.defs.ColorReplacements
|
||||
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
||||
import ru.dbotthepony.kstarbound.defs.TeamType
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatusControllerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
@JsonFactory
|
||||
data class MonsterVariant(
|
||||
val type: String,
|
||||
val shortDescription: String? = null,
|
||||
val description: String? = null,
|
||||
val seed: Long = 0L,
|
||||
val uniqueParameters: JsonObject = JsonObject(),
|
||||
val animationConfig: AnimationDefinition,
|
||||
val reversed: Boolean = false,
|
||||
val animatorPartTags: ImmutableMap<String, String>,
|
||||
val parameters: JsonObject,
|
||||
|
||||
@Deprecated("Raw property", replaceWith = ReplaceWith("this.actualDropPools"))
|
||||
val dropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class CommonParameters(
|
||||
val shortDescription: String? = null,
|
||||
val description: String? = null,
|
||||
val scripts: ImmutableList<AssetPath>,
|
||||
val animationScripts: ImmutableList<AssetPath> = ImmutableList.of(),
|
||||
val animationCustom: JsonElement = JsonNull.INSTANCE,
|
||||
val initialScriptDelta: Double = 5.0,
|
||||
val metaBoundBox: AABB,
|
||||
val scale: Double,
|
||||
val renderLayer: RenderLayer.Point = RenderLayer.Point(RenderLayer.Monster),
|
||||
val movementSettings: ActorMovementParameters = ActorMovementParameters.EMPTY,
|
||||
|
||||
val dropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>? = null,
|
||||
|
||||
val walkMultiplier: Double = 1.0,
|
||||
val runMultiplier: Double = 1.0,
|
||||
val jumpMultiplier: Double = 1.0,
|
||||
val weightMultiplier: Double = 1.0,
|
||||
val healthMultiplier: Double = 1.0,
|
||||
val touchDamageMultiplier: Double = 1.0,
|
||||
val touchDamage: JsonElement = JsonNull.INSTANCE,
|
||||
val animationDamageParts: JsonObject = JsonObject(),
|
||||
val statusSettings: StatusControllerConfig? = null,
|
||||
val knockoutTime: Double = 1.0,
|
||||
val knockoutEffect: String? = null,
|
||||
val knockoutAnimationStates: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
|
||||
// in pixels
|
||||
val mouthOffset: Vector2d,
|
||||
|
||||
// in pixels
|
||||
val feetOffset: Vector2d,
|
||||
|
||||
val powerLevelFunction: Registry.Ref<JsonFunction> = Registries.jsonFunctions.ref("monsterLevelPowerMultiplier"),
|
||||
val healthLevelFunction: Registry.Ref<JsonFunction> = Registries.jsonFunctions.ref("monsterLevelHealthMultiplier"),
|
||||
|
||||
val clientEntityMode: ClientEntityMode = ClientEntityMode.CLIENT_SLAVE_ONLY,
|
||||
val persistent: Boolean = false /* WHAT. */,
|
||||
val damageTeamType: TeamType = TeamType.ENEMY,
|
||||
val damageTeam: Int = 2,
|
||||
|
||||
val selfDamagePoly: Poly = movementSettings.standingPoly?.map({ it }, { it.firstOrNull() }) ?: Poly.EMPTY,
|
||||
val portraitIcon: String? = null,
|
||||
|
||||
val damageReceivedAggressiveDuration: Double = 1.0,
|
||||
val onDamagedOthersAggressiveDuration: Double = 5.0,
|
||||
val onFireAggressiveDuration: Double = 5.0,
|
||||
|
||||
val nametagColor: RGBAColor = RGBAColor.WHITE,
|
||||
val colorSwap: ColorReplacements? = null,
|
||||
) {
|
||||
val mouthOffsetTiles = mouthOffset / PIXELS_IN_STARBOUND_UNIT
|
||||
val feetOffsetTiles = feetOffset / PIXELS_IN_STARBOUND_UNIT
|
||||
}
|
||||
|
||||
val entry = Registries.monsterTypes.getOrThrow(type)
|
||||
val commonParameters = Starbound.gson.fromJsonFast(parameters, CommonParameters::class.java)
|
||||
|
||||
val animatorZoom: Double
|
||||
get() = commonParameters.scale
|
||||
|
||||
val actualDropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>
|
||||
get() = commonParameters.dropPools ?: dropPools
|
||||
|
||||
val chosenDropPool: Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>? = if (actualDropPools.isEmpty()) null else actualDropPools[staticRandomInt(0, actualDropPools.size - 1, seed, "MonsterDropPool")]
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
stream.writeBinaryString(type)
|
||||
stream.writeLong(seed)
|
||||
stream.writeJsonElement(uniqueParameters)
|
||||
} else {
|
||||
TODO("Native")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): MonsterVariant {
|
||||
if (isLegacy) {
|
||||
val name = stream.readBinaryString()
|
||||
val seed = stream.readLong()
|
||||
val uniqueParameters = stream.readJsonElement() as JsonObject
|
||||
|
||||
val base = Registries.monsterTypes.getOrThrow(name)
|
||||
return base.value.create(seed, uniqueParameters)
|
||||
} else {
|
||||
TODO("Native")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs.quest
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
|
||||
data class QuestGlobalConfig(
|
||||
val defaultIndicatorOffset: Vector2d = Vector2d.ZERO
|
||||
)
|
@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
@ -15,7 +16,7 @@ data class AsteroidWorldsConfig(
|
||||
val gravityRange: Vector2d,
|
||||
val worldSize: Vector2i,
|
||||
val threatRange: Vector2d,
|
||||
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val environmentStatusEffects: ImmutableSet<PersistentStatusEffect> = ImmutableSet.of(),
|
||||
val overrideTech: ImmutableSet<String>? = null,
|
||||
val globalDirectives: ImmutableSet<String>? = null,
|
||||
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in asteroid field.
|
||||
|
@ -21,6 +21,7 @@ data class Biome(
|
||||
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
|
||||
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
|
||||
val parallax: Parallax? = null,
|
||||
val spawnProfile: SpawnProfile? = null,
|
||||
) {
|
||||
@JvmName("hueShiftTile")
|
||||
fun hueShift(block: Registry.Entry<TileDefinition>): Float {
|
||||
|
@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
@ -9,6 +11,7 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
@ -21,7 +24,7 @@ import java.util.random.RandomGenerator
|
||||
data class BiomeDefinition(
|
||||
val airless: Boolean = false,
|
||||
val name: String,
|
||||
val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val statusEffects: ImmutableSet<PersistentStatusEffect> = ImmutableSet.of(),
|
||||
val weather: ImmutableList<Pair<Double, ImmutableList<AssetReference<WeightedList<String>>>>> = ImmutableList.of(), // binned reference to other assets
|
||||
val hueShiftOptions: ImmutableList<Double> = ImmutableList.of(),
|
||||
val skyOptions: ImmutableList<SkyColoring> = ImmutableList.of(),
|
||||
@ -34,6 +37,7 @@ data class BiomeDefinition(
|
||||
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||
val parallax: AssetReference<Parallax.Data>? = null,
|
||||
val spawnProfile: JsonObject? = null,
|
||||
) {
|
||||
data class CreationParams(
|
||||
val hueShift: Double,
|
||||
@ -82,7 +86,9 @@ data class BiomeDefinition(
|
||||
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
|
||||
.filter { it.first.native.isPresent }
|
||||
.collect(ImmutableList.toImmutableList())
|
||||
}?.orElse(ImmutableList.of()) ?: ImmutableList.of())
|
||||
}?.orElse(ImmutableList.of()) ?: ImmutableList.of()),
|
||||
|
||||
spawnProfile = spawnProfile?.let { SpawnProfile.create(it, random) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@ -17,7 +18,7 @@ data class DungeonWorldsConfig(
|
||||
val worldSize: Vector2i,
|
||||
val gravity: Either<Double, Vector2d>,
|
||||
val airless: Boolean = false,
|
||||
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val environmentStatusEffects: ImmutableSet<PersistentStatusEffect> = ImmutableSet.of(),
|
||||
val overrideTech: ImmutableSet<String>? = null,
|
||||
val globalDirectives: ImmutableSet<String>? = null,
|
||||
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in floating dungeon.
|
||||
|
@ -438,6 +438,7 @@ data class SkyGlobalConfig(
|
||||
val starVelocityFactor: Double = 0.0,
|
||||
val flyingTimer: Double = 0.0,
|
||||
val flashTimer: Double = 1.0,
|
||||
val dayTransitionTime: Double = 200.0,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class Stars(
|
||||
|
@ -0,0 +1,18 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import java.util.Collections
|
||||
import java.util.EnumSet
|
||||
|
||||
enum class SpawnArea(override val jsonName: String) : IStringSerializable {
|
||||
SURFACE("surface"),
|
||||
CEILING("ceiling"),
|
||||
AIR("air"),
|
||||
LIQUID("liquid"),
|
||||
SOLID("solid");
|
||||
|
||||
companion object {
|
||||
val ALL: Set<SpawnArea> = Collections.unmodifiableSet(EnumSet.allOf(SpawnArea::class.java))
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kstarbound.json.popObject
|
||||
import ru.dbotthepony.kstarbound.json.stream
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import java.util.EnumSet
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@JsonAdapter(SpawnParameters.Adapter::class)
|
||||
data class SpawnParameters(val areas: Set<SpawnArea> = SpawnArea.ALL, val region: SpawnRegion = SpawnRegion.ALL, val time: SpawnTime = SpawnTime.ALL) {
|
||||
fun isCompatible(other: SpawnParameters): Boolean {
|
||||
return region.isCompatible(other.region) && time.isCompatible(other.time) && areas.any { it in other.areas }
|
||||
}
|
||||
|
||||
class Adapter : TypeAdapter<SpawnParameters>() {
|
||||
override fun write(out: JsonWriter, value: SpawnParameters) {
|
||||
out.beginObject()
|
||||
out.name("areas")
|
||||
out.beginArray()
|
||||
value.areas.forEach { out.value(it.jsonName) }
|
||||
out.endArray()
|
||||
out.name("region")
|
||||
out.value(value.region.jsonName)
|
||||
out.name("time")
|
||||
out.value(value.time.jsonName)
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): SpawnParameters {
|
||||
val json = `in`.popObject()
|
||||
|
||||
val areas = if ("area" in json) {
|
||||
if (json["area"].asString == "all")
|
||||
SpawnArea.ALL
|
||||
else
|
||||
setOf(SpawnArea.entries.valueOf(json["area"].asString))
|
||||
} else if ("areas" in json) {
|
||||
json["areas"].asJsonArray.stream().map { SpawnArea.entries.valueOf(it.asString) }.collect(Collectors.toCollection { EnumSet.noneOf(SpawnArea::class.java) })
|
||||
} else {
|
||||
setOf()
|
||||
}
|
||||
|
||||
val region = SpawnRegion.entries.valueOf(json["region"].asString)
|
||||
val time = SpawnTime.entries.valueOf(json["time"].asString)
|
||||
return SpawnParameters(areas, region, time)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
@JsonFactory
|
||||
data class SpawnProfile(val spawnTypes: ImmutableSet<Registry.Ref<SpawnType>> = ImmutableSet.of(), val monsterParameters: JsonObject = JsonObject()) {
|
||||
companion object {
|
||||
fun create(json: JsonObject, random: RandomGenerator): SpawnProfile {
|
||||
val spawnTypes = ArrayList<Registry.Ref<SpawnType>>()
|
||||
|
||||
if ("groups" in json) {
|
||||
for (group in json["groups"].asJsonArray) {
|
||||
val pool = group.asJsonObject["pool"]
|
||||
val select = group.asJsonObject["select"].asJsonPrimitive.asInt
|
||||
|
||||
val typePool = if (pool is JsonPrimitive) {
|
||||
Globals.spawner.spawnGroups[pool.asString] ?: throw NoSuchElementException("No such common spawn group with name $pool")
|
||||
} else {
|
||||
Starbound.gson.fromJsonFast(pool)
|
||||
}
|
||||
|
||||
spawnTypes.addAll(typePool.sample(select, random))
|
||||
}
|
||||
}
|
||||
|
||||
return SpawnProfile(ImmutableSet.copyOf(spawnTypes), json["monsterParameters"] as? JsonObject ?: JsonObject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class SpawnRegion(override val jsonName: String, val enclosed: Boolean, val exposed: Boolean) : IStringSerializable {
|
||||
ALL("all", true, true) {
|
||||
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
ENCLOSED("enclosed", true, false) {
|
||||
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||
return other.enclosed
|
||||
}
|
||||
},
|
||||
|
||||
EXPOSED("exposed", false, true){
|
||||
override fun isCompatible(other: SpawnRegion): Boolean {
|
||||
return other.exposed
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun isCompatible(other: SpawnRegion): Boolean
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class SpawnTime(override val jsonName: String, val day: Boolean, val night: Boolean) : IStringSerializable {
|
||||
ALL("all", true, true) {
|
||||
override fun isCompatible(other: SpawnTime): Boolean {
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
DAY("day", true, false) {
|
||||
override fun isCompatible(other: SpawnTime): Boolean {
|
||||
return other.day
|
||||
}
|
||||
},
|
||||
|
||||
NIGHT("night", false, true) {
|
||||
override fun isCompatible(other: SpawnTime): Boolean {
|
||||
return other.night
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun isCompatible(other: SpawnTime): Boolean
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.util.XXHash64
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
|
||||
@JsonFactory
|
||||
data class SpawnType(
|
||||
val name: String,
|
||||
val dayLevelAdjustment: Vector2d = Vector2d.ZERO,
|
||||
val nightLevelAdjustment: Vector2d = Vector2d.ZERO,
|
||||
|
||||
val monsterType: Either<WeightedList<Registry.Ref<MonsterTypeDefinition>>, Registry.Ref<MonsterTypeDefinition>>,
|
||||
val monsterParameters: JsonObject = JsonObject(),
|
||||
val spawnParameters: SpawnParameters = SpawnParameters(),
|
||||
val groupSize: Vector2i = Vector2i.POSITIVE_XY,
|
||||
val spawnChance: Double,
|
||||
// tard
|
||||
@Deprecated("Raw property", replaceWith = ReplaceWith("this.actualSeedMix"))
|
||||
val seedMix: Long? = null,
|
||||
) {
|
||||
val actualSeedMix: Long
|
||||
|
||||
init {
|
||||
if (monsterType.isLeft && monsterType.left().isEmpty)
|
||||
throw IllegalArgumentException("monsterType is empty")
|
||||
|
||||
if (seedMix != null) {
|
||||
actualSeedMix = seedMix
|
||||
} else {
|
||||
val hasher = XXHash64()
|
||||
hasher.update(name.toByteArray(Charsets.UTF_8))
|
||||
actualSeedMix = hasher.digestAsLong()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class SpawnerConfig(
|
||||
val spawnCellSize: Int,
|
||||
val spawnCellMinimumEmptyTiles: Int,
|
||||
val spawnCellMinimumLiquidTiles: Int,
|
||||
val spawnCellMinimumNearSurfaceTiles: Int,
|
||||
val spawnCellMinimumNearCeilingTiles: Int,
|
||||
val spawnCellMinimumAirTiles: Int,
|
||||
val spawnCellMinimumExposedTiles: Int,
|
||||
val spawnCellNearSurfaceDistance: Int,
|
||||
val spawnCellNearCeilingDistance: Int,
|
||||
|
||||
val minimumDayLevel: Double,
|
||||
val minimumLiquidLevel: Double,
|
||||
val spawnCheckResolution: Double,
|
||||
val spawnSurfaceCheckDistance: Int,
|
||||
val spawnCeilingCheckDistance: Int,
|
||||
val spawnProhibitedCheckPadding: Double,
|
||||
|
||||
val spawnCellLifetime: Double,
|
||||
val windowActivationBorder: Int,
|
||||
|
||||
val defaultActive: Boolean = true,
|
||||
val debug: Boolean = false,
|
||||
|
||||
val spawnGroups: ImmutableMap<String, WeightedList<Registry.Ref<SpawnType>>> = ImmutableMap.of(),
|
||||
) {
|
||||
init {
|
||||
require(spawnCellSize >= 1) { "Bad spawnCellSize: $spawnCellSize" }
|
||||
require(spawnCellMinimumEmptyTiles >= 0) { "Negative spawnCellMinimumEmptyTiles: $spawnCellMinimumEmptyTiles" }
|
||||
require(spawnCellMinimumLiquidTiles >= 0) { "Negative spawnCellMinimumLiquidTiles: $spawnCellMinimumLiquidTiles" }
|
||||
require(spawnCellMinimumNearSurfaceTiles >= 0) { "Negative spawnCellMinimumNearSurfaceTiles: $spawnCellMinimumNearSurfaceTiles" }
|
||||
require(spawnCellMinimumNearCeilingTiles >= 0) { "Negative spawnCellMinimumNearCeilingTiles: $spawnCellMinimumNearCeilingTiles" }
|
||||
require(spawnCellMinimumAirTiles >= 0) { "Negative spawnCellMinimumAirTiles: $spawnCellMinimumAirTiles" }
|
||||
require(spawnCellMinimumExposedTiles >= 0) { "Negative spawnCellMinimumExposedTiles: $spawnCellMinimumExposedTiles" }
|
||||
require(spawnCellNearSurfaceDistance >= 0) { "Negative spawnCellNearSurfaceDistance: $spawnCellNearSurfaceDistance" }
|
||||
require(spawnCellNearCeilingDistance >= 0) { "Negative spawnCellNearCeilingDistance: $spawnCellNearCeilingDistance" }
|
||||
|
||||
require(windowActivationBorder >= 0) { "Negative windowActivationBorder: $windowActivationBorder" }
|
||||
}
|
||||
}
|
@ -540,7 +540,7 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
|
||||
while (indices.isNotEmpty()) {
|
||||
val v = indices.random(random)
|
||||
shuffled.add(v)
|
||||
indices.removeInt(indices.indexOf(v))
|
||||
indices.rem(v)
|
||||
}
|
||||
|
||||
while (secondaryRegionCount-- > 0 && shuffled.isNotEmpty()) {
|
||||
|
@ -23,10 +23,15 @@ import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.EphemeralStatusEffect
|
||||
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.io.readDouble
|
||||
@ -110,7 +115,7 @@ abstract class VisitableWorldParameters {
|
||||
protected set
|
||||
var airless: Boolean = false
|
||||
protected set
|
||||
var environmentStatusEffects: Set<String> by Delegates.notNull()
|
||||
var environmentStatusEffects: Set<PersistentStatusEffect> by Delegates.notNull()
|
||||
protected set
|
||||
var overrideTech: Set<String>? = null
|
||||
protected set
|
||||
@ -140,7 +145,7 @@ abstract class VisitableWorldParameters {
|
||||
val worldSize: Vector2i,
|
||||
val gravity: Either<Vector2d, Double>,
|
||||
val airless: Boolean,
|
||||
val environmentStatusEffects: Set<String>,
|
||||
val environmentStatusEffects: Set<PersistentStatusEffect>,
|
||||
val overrideTech: Set<String>? = null,
|
||||
val globalDirectives: Set<String>? = null,
|
||||
val beamUpRule: BeamUpRule,
|
||||
@ -211,7 +216,7 @@ abstract class VisitableWorldParameters {
|
||||
if (collection.isNotEmpty())
|
||||
weatherPool = WeightedList(ImmutableList.copyOf(collection))
|
||||
|
||||
environmentStatusEffects = ImmutableSet.copyOf(stream.readCollection { readInternedString() })
|
||||
environmentStatusEffects = ImmutableSet.copyOf(stream.readCollection { Either.right(Registries.statusEffects.ref(readInternedString())) })
|
||||
overrideTech = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
|
||||
globalDirectives = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
|
||||
|
||||
@ -234,7 +239,7 @@ abstract class VisitableWorldParameters {
|
||||
else
|
||||
stream.writeCollection(weatherPool!!.parent) { writeDouble(it.first); writeBinaryString(it.second) }
|
||||
|
||||
stream.writeCollection(environmentStatusEffects) { writeBinaryString(it) }
|
||||
stream.writeCollection(environmentStatusEffects.filter { it.isRight }.map { it.right() }) { writeBinaryString(it.key.left()) }
|
||||
stream.writeNullable(overrideTech) { writeCollection(it) { writeBinaryString(it) } }
|
||||
stream.writeNullable(globalDirectives) { writeCollection(it) { writeBinaryString(it) } }
|
||||
stream.writeByte(beamUpRule.ordinal)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
|
||||
class WorldServerConfig(
|
||||
val playerSpaceStartRegionSize: Vector2d,
|
@ -6,11 +6,15 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.math.AABBi
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.util.IStruct2d
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
|
@ -1,6 +1,8 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
|
||||
@JsonFactory
|
||||
data class WorldTemplateConfig(
|
||||
@ -14,4 +16,8 @@ data class WorldTemplateConfig(
|
||||
val customTerrainBlendSize: Double = 0.0,
|
||||
val surfaceCaveAttenuationDist: Double = 0.0,
|
||||
val surfaceCaveAttenuationFactor: Double = 1.0,
|
||||
)
|
||||
|
||||
val defaultGravity: Either<Vector2d, Double>,
|
||||
) {
|
||||
val defaultGravityVector = defaultGravity.map({ it }, { Vector2d(0.0, it) })
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList
|
||||
import ru.dbotthepony.kommons.io.ByteKey
|
||||
import ru.dbotthepony.kommons.io.readByteKeyRaw
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import java.io.Closeable
|
||||
|
103
src/main/kotlin/ru/dbotthepony/kstarbound/io/ByteKey.kt
Normal file
103
src/main/kotlin/ru/dbotthepony/kstarbound/io/ByteKey.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
private fun toBytes(key: UUID): ByteArray {
|
||||
val value = ByteArray(16)
|
||||
|
||||
value[0] = ((key.mostSignificantBits ushr 56) and 0xFFL).toByte()
|
||||
value[1] = ((key.mostSignificantBits ushr 48) and 0xFFL).toByte()
|
||||
value[2] = ((key.mostSignificantBits ushr 40) and 0xFFL).toByte()
|
||||
value[3] = ((key.mostSignificantBits ushr 32) and 0xFFL).toByte()
|
||||
value[4] = ((key.mostSignificantBits ushr 24) and 0xFFL).toByte()
|
||||
value[5] = ((key.mostSignificantBits ushr 16) and 0xFFL).toByte()
|
||||
value[6] = ((key.mostSignificantBits ushr 8) and 0xFFL).toByte()
|
||||
value[7] = ((key.mostSignificantBits ushr 0) and 0xFFL).toByte()
|
||||
|
||||
value[8 + 0] = ((key.leastSignificantBits ushr 56) and 0xFFL).toByte()
|
||||
value[8 + 1] = ((key.leastSignificantBits ushr 48) and 0xFFL).toByte()
|
||||
value[8 + 2] = ((key.leastSignificantBits ushr 40) and 0xFFL).toByte()
|
||||
value[8 + 3] = ((key.leastSignificantBits ushr 32) and 0xFFL).toByte()
|
||||
value[8 + 4] = ((key.leastSignificantBits ushr 24) and 0xFFL).toByte()
|
||||
value[8 + 5] = ((key.leastSignificantBits ushr 16) and 0xFFL).toByte()
|
||||
value[8 + 6] = ((key.leastSignificantBits ushr 8) and 0xFFL).toByte()
|
||||
value[8 + 7] = ((key.leastSignificantBits ushr 0) and 0xFFL).toByte()
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
class ByteKey private constructor(private val bytes: ByteArray, mark: Nothing?) : Comparable<ByteKey> {
|
||||
constructor(vararg bytes: Byte) : this(bytes, null)
|
||||
constructor(key: String) : this(key.toByteArray(), null)
|
||||
constructor(key: UUID) : this(toBytes(key), null)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is ByteKey && other.bytes.contentEquals(bytes)
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = bytes.size
|
||||
|
||||
operator fun get(index: Int): Byte {
|
||||
return bytes[index]
|
||||
}
|
||||
|
||||
fun write(stream: OutputStream) {
|
||||
stream.writeVarInt(bytes.size)
|
||||
stream.write(bytes)
|
||||
}
|
||||
|
||||
fun writeRaw(stream: OutputStream) {
|
||||
stream.write(bytes)
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
return bytes.clone()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return bytes.contentHashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ByteKey[${bytes.map { it.toInt() and 0xFF }.joinToString(", ")}]"
|
||||
}
|
||||
|
||||
override fun compareTo(other: ByteKey): Int {
|
||||
val cmp = size.compareTo(other.size)
|
||||
if (cmp != 0) return cmp
|
||||
|
||||
for (i in bytes.indices) {
|
||||
if (bytes[i].toInt() and 0xFF > other.bytes[i].toInt() and 0xFF) {
|
||||
return 1
|
||||
} else if (bytes[i].toInt() and 0xFF < other.bytes[i].toInt() and 0xFF) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Constructs [ByteKey] without any copying of provided array
|
||||
*/
|
||||
@JvmStatic
|
||||
fun wrap(bytes: ByteArray): ByteKey {
|
||||
return ByteKey(bytes, null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun read(stream: InputStream): ByteKey {
|
||||
return stream.readByteKey()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun readRaw(stream: InputStream, size: Int): ByteKey {
|
||||
return stream.readByteKeyRaw(size)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import ru.dbotthepony.kommons.io.readFloat
|
||||
import ru.dbotthepony.kommons.io.readInt
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.readSignedVarLong
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeDouble
|
||||
@ -322,3 +323,19 @@ fun InputStream.readDouble(precision: Double, isLegacy: Boolean): Double {
|
||||
return readDouble()
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.readByteKey(): ByteKey {
|
||||
return ByteKey(*ByteArray(readVarInt()).also { read(it) })
|
||||
}
|
||||
|
||||
fun InputStream.readByteKeyRaw(size: Int): ByteKey {
|
||||
return ByteKey(*ByteArray(size).also { read(it) })
|
||||
}
|
||||
|
||||
fun OutputStream.writeByteKey(key: ByteKey) {
|
||||
key.write(this)
|
||||
}
|
||||
|
||||
fun OutputStream.writeRawByteKey(key: ByteKey) {
|
||||
key.writeRaw(this)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.fromJsonTreeFast
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.Future
|
||||
@ -50,7 +51,6 @@ object ItemRegistry {
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val entries = HashMap<String, Entry>()
|
||||
private val tasks = ConcurrentLinkedQueue<Runnable>()
|
||||
|
||||
val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null)
|
||||
|
||||
@ -134,9 +134,6 @@ object ItemRegistry {
|
||||
}
|
||||
|
||||
fun finishLoad() {
|
||||
tasks.forEach { it.run() }
|
||||
tasks.clear()
|
||||
|
||||
for (obj in Registries.worldObjects.keys.values) {
|
||||
if (obj.value.hasObjectItem) {
|
||||
addObjectItem(obj)
|
||||
@ -154,10 +151,10 @@ object ItemRegistry {
|
||||
for (file in files) {
|
||||
futures.add(Starbound.GLOBAL_SCOPE.launch {
|
||||
try {
|
||||
val read = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(file.asyncJsonReader().await()), patches[file.computeFullPath()]).asJsonObject
|
||||
val readData = data.fromJsonTree(read)
|
||||
val read = JsonPatch.applyAsync(file.asyncJsonReader(), patches[file.computeFullPath()]).asJsonObject
|
||||
val readData = data.fromJsonTreeFast(read)
|
||||
|
||||
tasks.add {
|
||||
Starbound.submit {
|
||||
if (readData.itemName in entries) {
|
||||
LOGGER.warn("Overwriting item definition at ${readData.itemName} (old def originate from ${entries[readData.itemName]!!.file}; new from $file)")
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ import kotlin.properties.Delegates
|
||||
@JsonAdapter(ItemStack.Adapter::class)
|
||||
open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, parameters: JsonObject, size: Long) {
|
||||
/**
|
||||
* unique number utilized to determine whenever stack has changed
|
||||
* Monotonically increasing global number utilized to determine whenever stack has changed
|
||||
*/
|
||||
var changeset: Long = CHANGESET.incrementAndGet()
|
||||
private set
|
||||
|
@ -32,40 +32,6 @@ object RecipeRegistry {
|
||||
val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking)
|
||||
val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking)
|
||||
|
||||
private val tasks = ConcurrentLinkedQueue<Entry>()
|
||||
|
||||
private fun add(recipe: Entry) {
|
||||
val value = recipe.value
|
||||
recipesInternal.add(recipe)
|
||||
|
||||
for (group in value.groups) {
|
||||
group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
group2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
}
|
||||
|
||||
output2recipesInternal.computeIfAbsent(value.output.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
output2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
|
||||
for (input in value.input) {
|
||||
input2recipesInternal.computeIfAbsent(input.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
}
|
||||
}
|
||||
|
||||
fun finishLoad() {
|
||||
tasks.forEach { add(it) }
|
||||
tasks.clear()
|
||||
}
|
||||
|
||||
fun load(fileTree: Map<String, Collection<IStarboundFile>>, patchTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
|
||||
val files = fileTree["recipe"] ?: return emptyList()
|
||||
|
||||
@ -75,9 +41,34 @@ object RecipeRegistry {
|
||||
Starbound.EXECUTOR.submit {
|
||||
try {
|
||||
val json = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patchTree[listedFile.computeFullPath()])
|
||||
|
||||
val value = recipes.fromJsonTree(json)
|
||||
tasks.add(Entry(value, json, listedFile))
|
||||
val recipe = Entry(value, json, listedFile)
|
||||
|
||||
Starbound.submit {
|
||||
recipesInternal.add(recipe)
|
||||
|
||||
for (group in value.groups) {
|
||||
group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
group2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
}
|
||||
|
||||
output2recipesInternal.computeIfAbsent(value.output.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
output2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
|
||||
for (input in value.input) {
|
||||
input2recipesInternal.computeIfAbsent(input.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
}
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Loading recipe definition file $listedFile", err)
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.stream.JsonReader
|
||||
import kotlinx.coroutines.future.await
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kstarbound.IStarboundFile
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
enum class JsonPatch(val key: String) {
|
||||
TEST("test") {
|
||||
@ -165,5 +167,25 @@ enum class JsonPatch(val key: String) {
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
suspend fun applyAsync(reader: CompletableFuture<JsonReader>, source: Collection<IStarboundFile>?): JsonElement {
|
||||
source ?: return Starbound.ELEMENTS_ADAPTER.read(reader.await())
|
||||
val loaded = source.map { it.asyncJsonReader() }
|
||||
var base = Starbound.ELEMENTS_ADAPTER.read(reader.await())
|
||||
val itr = source.iterator()
|
||||
|
||||
for (patch in loaded) {
|
||||
val file = itr.next()
|
||||
val read = Starbound.ELEMENTS_ADAPTER.read(patch.await())
|
||||
|
||||
if (read !is JsonArray) {
|
||||
LOGGER.error("$patch root element is not an array")
|
||||
} else {
|
||||
base = apply(base, read, file)
|
||||
}
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package ru.dbotthepony.kstarbound.json.factory
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kstarbound.json.FastJsonTreeReader
|
||||
|
||||
class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
||||
class Immutable2MapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
||||
override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
@ -30,6 +31,21 @@ class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val ele
|
||||
if (reader.consumeNull())
|
||||
return null
|
||||
|
||||
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
|
||||
reader.beginObject()
|
||||
|
||||
val builder = ImmutableMap.Builder<K, V>()
|
||||
|
||||
while (reader.peek() !== JsonToken.END_OBJECT) {
|
||||
builder.put(
|
||||
keyAdapter.read(FastJsonTreeReader(JsonPrimitive(reader.nextName()))) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}"),
|
||||
elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}"))
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
reader.beginArray()
|
||||
|
||||
val builder = ImmutableMap.Builder<K, V>()
|
@ -31,7 +31,7 @@ class ImmutableCollectionAdapterFactory(val stringInterner: Interner<String> = I
|
||||
return ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T>
|
||||
}
|
||||
|
||||
return ImmutableArrayMapTypeAdapter(
|
||||
return Immutable2MapTypeAdapter(
|
||||
gson.getAdapter(TypeToken.get(elementType0)),
|
||||
gson.getAdapter(TypeToken.get(elementType1))
|
||||
) as TypeAdapter<T>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.json.factory
|
||||
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
@ -29,6 +30,12 @@ object RGBAColorTypeAdapter : TypeAdapter<RGBAColor>() {
|
||||
if (`in`.consumeNull())
|
||||
return null
|
||||
|
||||
if (`in`.peek() == JsonToken.STRING) {
|
||||
val str = `in`.nextString()
|
||||
val color = RGBAColor.fromHexStringRGB(str) ?: throw JsonSyntaxException("Not a valid hex color: $str")
|
||||
return color
|
||||
}
|
||||
|
||||
`in`.beginArray()
|
||||
|
||||
val red = `in`.nextInt()
|
||||
|
@ -29,11 +29,13 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||
import ru.dbotthepony.kstarbound.math.AABBi
|
||||
import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
|
||||
fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
@ -44,6 +46,7 @@ fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
||||
fun ExecutionContext.toVector2d(table: Any): Vector2d {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
@ -57,6 +60,10 @@ fun ExecutionContext.toLine2d(table: Any): Line2d {
|
||||
return Line2d(p0, p1)
|
||||
}
|
||||
|
||||
fun String?.toByteString(): ByteString? {
|
||||
return if (this == null) null else ByteString.of(this)
|
||||
}
|
||||
|
||||
fun ExecutionContext.toPoly(table: Table): Poly {
|
||||
val vertices = ArrayList<Vector2d>()
|
||||
|
||||
@ -70,6 +77,7 @@ fun ExecutionContext.toPoly(table: Table): Poly {
|
||||
fun ExecutionContext.toVector2f(table: Any): Vector2f {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
@ -82,6 +90,7 @@ fun ExecutionContext.toColor(table: Any): RGBAColor {
|
||||
val y = indexNoYield(table, 2L)
|
||||
val z = indexNoYield(table, 3L)
|
||||
val w = indexNoYield(table, 4L) ?: 255
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y")
|
||||
@ -96,6 +105,7 @@ fun ExecutionContext.toAABB(table: Any): AABB {
|
||||
val y = indexNoYield(table, 2L)
|
||||
val z = indexNoYield(table, 3L)
|
||||
val w = indexNoYield(table, 4L)
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y")
|
||||
@ -110,6 +120,7 @@ fun ExecutionContext.toAABBi(table: Any): AABBi {
|
||||
val y = indexNoYield(table, 2L)
|
||||
val z = indexNoYield(table, 3L)
|
||||
val w = indexNoYield(table, 4L)
|
||||
returnBuffer.setTo()
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [2] is not a number: $y")
|
||||
@ -122,8 +133,8 @@ fun ExecutionContext.toAABBi(table: Any): AABBi {
|
||||
fun toJsonFromLua(value: Any?): JsonElement {
|
||||
return when (value) {
|
||||
null, is JsonNull -> JsonNull.INSTANCE
|
||||
is String -> JsonPrimitive(value)
|
||||
is ByteString -> JsonPrimitive(value.decode())
|
||||
is String -> JsonPrimitive(value.sbIntern())
|
||||
is ByteString -> JsonPrimitive(value.decode().sbIntern())
|
||||
is Number -> JsonPrimitive(value)
|
||||
is Boolean -> InternedJsonElementAdapter.of(value)
|
||||
is Table -> value.toJson()
|
||||
@ -320,6 +331,22 @@ fun TableFactory.from(value: JsonObject): Table {
|
||||
return data
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: Int?): Long? {
|
||||
return value?.toLong()
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: Long?): Long? {
|
||||
return value
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: String?): ByteString? {
|
||||
return value.toByteString()
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: ByteString?): ByteString? {
|
||||
return value
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonArray): Table {
|
||||
val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size())
|
||||
|
||||
|
@ -18,6 +18,10 @@ import org.classdump.luna.runtime.Dispatch
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
||||
import java.util.Spliterator
|
||||
import java.util.Spliterators
|
||||
import java.util.stream.Stream
|
||||
import java.util.stream.StreamSupport
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -111,6 +115,14 @@ operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
||||
}
|
||||
}
|
||||
|
||||
fun Table.spliterator(): Spliterator<Map.Entry<Any, Any>> {
|
||||
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED)
|
||||
}
|
||||
|
||||
fun Table.stream(): Stream<Map.Entry<Any, Any>> {
|
||||
return StreamSupport.stream(spliterator(), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* to be used in places where we need to "unpack" table, like this:
|
||||
*
|
||||
|
@ -12,6 +12,8 @@ import org.classdump.luna.Variable
|
||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||
import org.classdump.luna.compiler.CompilerSettings
|
||||
import org.classdump.luna.env.RuntimeEnvironments
|
||||
import org.classdump.luna.exec.CallPausedException
|
||||
import org.classdump.luna.exec.Continuation
|
||||
import org.classdump.luna.exec.DirectCallExecutor
|
||||
import org.classdump.luna.impl.DefaultTable
|
||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
@ -24,6 +26,7 @@ import org.classdump.luna.lib.TableLib
|
||||
import org.classdump.luna.lib.Utf8Lib
|
||||
import org.classdump.luna.load.ChunkFactory
|
||||
import org.classdump.luna.runtime.AbstractFunction1
|
||||
import org.classdump.luna.runtime.Coroutine
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
@ -158,8 +161,6 @@ class LuaEnvironment : StateContext {
|
||||
|
||||
globals["print"] = PrintFunction(globals)
|
||||
|
||||
globals["require"] = LuaRequire()
|
||||
|
||||
// why not use _ENV anyway lol
|
||||
globals["self"] = newTable()
|
||||
|
||||
@ -212,6 +213,7 @@ class LuaEnvironment : StateContext {
|
||||
private val scripts = ObjectArraySet<ChunkFactory>()
|
||||
private var initCalled = false
|
||||
private val loadedScripts = ObjectArraySet<String>()
|
||||
val require = LuaRequire()
|
||||
|
||||
inner class LuaRequire : AbstractFunction1<ByteString>() {
|
||||
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
|
||||
@ -228,13 +230,32 @@ class LuaEnvironment : StateContext {
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
globals["require"] = require
|
||||
}
|
||||
|
||||
fun attach(script: ChunkFactory) {
|
||||
scripts.add(script)
|
||||
if (initCalled) {
|
||||
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
|
||||
} else {
|
||||
scripts.add(script)
|
||||
}
|
||||
}
|
||||
|
||||
fun attach(scripts: Collection<AssetPath>) {
|
||||
for (script in scripts) {
|
||||
attach(Starbound.loadScript(script.fullPath))
|
||||
if (initCalled) {
|
||||
for (name in scripts) {
|
||||
if (loadedScripts.add(name.fullPath)) {
|
||||
val script = Starbound.loadScript(name.fullPath)
|
||||
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (script in scripts) {
|
||||
if (loadedScripts.add(script.fullPath)) {
|
||||
this.scripts.add(Starbound.loadScript(script.fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,6 +270,8 @@ class LuaEnvironment : StateContext {
|
||||
errorState = true
|
||||
}
|
||||
|
||||
fun call(fn: Any, vararg args: Any?) = executor.call(this, fn, *args)
|
||||
|
||||
fun init(callInit: Boolean = true): Boolean {
|
||||
check(!initCalled) { "Already called init()" }
|
||||
initCalled = true
|
||||
@ -257,7 +280,7 @@ class LuaEnvironment : StateContext {
|
||||
try {
|
||||
executor.call(this, script.newInstance(Variable(globals)))
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Failed to attach script to environment", err)
|
||||
scripts.clear()
|
||||
return false
|
||||
@ -273,7 +296,7 @@ class LuaEnvironment : StateContext {
|
||||
try {
|
||||
executor.call(this, init)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Exception on init()", err)
|
||||
return false
|
||||
}
|
||||
@ -284,7 +307,7 @@ class LuaEnvironment : StateContext {
|
||||
}
|
||||
|
||||
fun invokeGlobal(name: String, vararg arguments: Any?): Array<Any?> {
|
||||
if (errorState)
|
||||
if (errorState || !initCalled)
|
||||
return arrayOf()
|
||||
|
||||
val load = globals[name]
|
||||
@ -293,7 +316,7 @@ class LuaEnvironment : StateContext {
|
||||
return try {
|
||||
executor.call(this, load, *arguments)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
// errorState = true
|
||||
LOGGER.error("Exception while calling global $name", err)
|
||||
arrayOf()
|
||||
}
|
||||
|
@ -30,15 +30,15 @@ class LuaMessageHandlerComponent(val lua: LuaEnvironment, val nameProvider: () -
|
||||
|
||||
private val logPacer = ActionPacer(1, 5)
|
||||
|
||||
fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement {
|
||||
val handler = handlers[message] ?: throw World.MessageCallException("No registered handler for $message")
|
||||
fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement? {
|
||||
val handler = handlers[message] ?: return null
|
||||
|
||||
try {
|
||||
val unpack = arguments.map { lua.from(it) }.toTypedArray()
|
||||
val result = lua.executor.call(lua, handler, isLocal, *unpack)
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return JsonNull.INSTANCE
|
||||
return null
|
||||
} else {
|
||||
return toJsonFromLua(result[0])
|
||||
}
|
||||
|
@ -19,6 +19,19 @@ class LuaUpdateComponent(val lua: LuaEnvironment) {
|
||||
}
|
||||
}
|
||||
|
||||
fun update(delta: Double, preRun: () -> Unit) {
|
||||
if (stepCount == 0.0)
|
||||
return
|
||||
|
||||
steps += delta / Starbound.TIMESTEP
|
||||
|
||||
if (steps >= stepCount) {
|
||||
steps %= stepCount
|
||||
preRun()
|
||||
lua.invokeGlobal("update", stepCount * Starbound.TIMESTEP)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(delta: Double, vararg arguments: Any?) {
|
||||
if (stepCount == 0.0)
|
||||
return
|
||||
|
@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toColor
|
||||
import ru.dbotthepony.kstarbound.lua.toPoly
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
@ -71,7 +72,7 @@ fun provideAnimatorBindings(self: Animator, lua: LuaEnvironment) {
|
||||
callbacks["rotationGroups"] = luaFunction {
|
||||
val groups = self.rotationGroups()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v.toByteString() }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
|
@ -6,26 +6,59 @@ import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ActorEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
|
||||
fun provideEntityBindings(self: AbstractEntity, lua: LuaEnvironment) {
|
||||
if (self is WorldObject)
|
||||
provideWorldObjectBindings(self, lua)
|
||||
|
||||
if (self is MonsterEntity)
|
||||
provideMonsterBindings(self, lua)
|
||||
|
||||
if (self is ActorEntity)
|
||||
provideStatusControllerBindings(self.statusController, lua)
|
||||
|
||||
provideWorldBindings(self.world, lua)
|
||||
|
||||
val table = lua.newTable()
|
||||
lua.globals["entity"] = table
|
||||
|
||||
table["id"] = luaFunction { returnBuffer.setTo(self.entityID) }
|
||||
table["id"] = luaFunction { returnBuffer.setTo(self.entityID.toLong()) }
|
||||
table["position"] = luaFunction { returnBuffer.setTo(from(self.position)) }
|
||||
table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName) }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get()) }
|
||||
table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName.toByteString()) }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) }
|
||||
table["persistent"] = luaFunction { returnBuffer.setTo(self.isPersistent) }
|
||||
|
||||
table["entityInSight"] = luaFunction { TODO() }
|
||||
table["isValidTarget"] = luaFunction { TODO() }
|
||||
table["entityInSight"] = luaFunction { target: Number ->
|
||||
val entity = self.world.entities[target.toInt()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||
returnBuffer.setTo(self.world.chunkMap.collide(Line2d(self.position, entity.position)) { it.type.isSolidCollision } == null)
|
||||
}
|
||||
|
||||
table["isValidTarget"] = luaFunction { target: Number ->
|
||||
val entity = self.world.entities[target.toInt()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||
|
||||
if (!self.team.get().canDamage(entity.team.get(), self == entity)) // original engine always passes `false` to `isSelfDamage`
|
||||
return@luaFunction returnBuffer.setTo(false)
|
||||
|
||||
if (entity is MonsterEntity)
|
||||
return@luaFunction returnBuffer.setTo(entity.isAggressive)
|
||||
|
||||
// TODO: NPC handling here
|
||||
|
||||
returnBuffer.setTo(entity is PlayerEntity)
|
||||
}
|
||||
|
||||
table["damageTeam"] = luaFunction {
|
||||
val result = newTable()
|
||||
|
||||
result["team"] = self.team.get().team
|
||||
result["type"] = self.type.jsonName
|
||||
result["team"] = self.team.get().team.toLong()
|
||||
result["type"] = self.type.jsonName.toByteString()
|
||||
|
||||
returnBuffer.setTo(result)
|
||||
}
|
||||
|
@ -0,0 +1,193 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.collect.collect
|
||||
import ru.dbotthepony.kommons.collect.map
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
||||
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.stream
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import ru.dbotthepony.kstarbound.world.entities.ActorEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
|
||||
|
||||
fun provideMonsterBindings(self: MonsterEntity, lua: LuaEnvironment) {
|
||||
val config = lua.newTable()
|
||||
lua.globals["config"] = config
|
||||
|
||||
config["getParameter"] = createConfigBinding { key, default ->
|
||||
key.find(self.variant.parameters) ?: default
|
||||
}
|
||||
|
||||
val callbacks = lua.newTable()
|
||||
lua.globals["monster"] = callbacks
|
||||
|
||||
callbacks["type"] = luaFunction {
|
||||
returnBuffer.setTo(self.variant.type)
|
||||
}
|
||||
|
||||
callbacks["seed"] = luaFunction {
|
||||
// what the fuck.
|
||||
returnBuffer.setTo(self.variant.seed.toString())
|
||||
}
|
||||
|
||||
callbacks["seedNumber"] = luaFunction {
|
||||
returnBuffer.setTo(self.variant.seed)
|
||||
}
|
||||
|
||||
callbacks["uniqueParameters"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.variant.uniqueParameters))
|
||||
}
|
||||
|
||||
callbacks["level"] = luaFunction {
|
||||
// TODO: this makes half sense
|
||||
returnBuffer.setTo(self.monsterLevel ?: 0.0)
|
||||
}
|
||||
|
||||
callbacks["setDamageOnTouch"] = luaFunction { damage: Boolean ->
|
||||
self.damageOnTouch = damage
|
||||
}
|
||||
|
||||
callbacks["setDamageSources"] = luaFunction { sources: Table? ->
|
||||
self.customDamageSources.clear()
|
||||
|
||||
if (sources != null) {
|
||||
for ((_, v) in sources) {
|
||||
self.customDamageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["setDamageParts"] = luaFunction { parts: Table? ->
|
||||
if (parts == null) {
|
||||
self.animationDamageParts.clear()
|
||||
} else {
|
||||
val strings = parts.stream().map { (_, v) -> v.toString() }.collect(ImmutableSet.toImmutableSet())
|
||||
self.animationDamageParts.removeIf { it !in strings }
|
||||
|
||||
for (v in strings) {
|
||||
if (v !in self.animationDamageParts) {
|
||||
self.animationDamageParts.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["setAggressive"] = luaFunction { isAggressive: Boolean ->
|
||||
self.isAggressive = isAggressive
|
||||
}
|
||||
|
||||
callbacks["setActiveSkillName"] = luaFunction { name: ByteString ->
|
||||
self.activeSkillName = name.decode().sbIntern()
|
||||
}
|
||||
|
||||
callbacks["setDropPool"] = luaFunction { pool: Any ->
|
||||
self.dropPool = Starbound.gson.fromJsonFast(toJsonFromLua(pool))
|
||||
}
|
||||
|
||||
callbacks["toAbsolutePosition"] = luaFunction { position: Table ->
|
||||
returnBuffer.setTo(from(self.movement.getAbsolutePosition(toVector2d(position))))
|
||||
}
|
||||
|
||||
callbacks["mouthPosition"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.mouthPosition))
|
||||
}
|
||||
|
||||
// This callback is registered here rather than in
|
||||
// makeActorMovementControllerCallbacks
|
||||
// because it requires access to world
|
||||
callbacks["flyTo"] = luaFunction { position: Table ->
|
||||
self.movement.controlFly = self.world.geometry.diff(toVector2d(position), self.movement.position)
|
||||
}
|
||||
|
||||
callbacks["setDeathParticleBurst"] = luaFunction { value: ByteString? ->
|
||||
self.deathParticlesBurst = value?.decode()?.sbIntern() ?: ""
|
||||
}
|
||||
|
||||
callbacks["setDeathSound"] = luaFunction { value: ByteString? ->
|
||||
self.deathSound = value?.decode()?.sbIntern() ?: ""
|
||||
}
|
||||
|
||||
callbacks["setPhysicsForces"] = luaFunction { forces: Table? ->
|
||||
if (forces == null) {
|
||||
self.forceRegions.clear()
|
||||
} else {
|
||||
self.forceRegions.clear()
|
||||
|
||||
for ((_, v) in forces) {
|
||||
self.forceRegions.add(Starbound.gson.fromJsonFast(toJsonFromLua(v), PhysicsForceRegion::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["setName"] = luaFunction { name: ByteString? ->
|
||||
self.networkName = name?.decode()
|
||||
}
|
||||
|
||||
callbacks["setDisplayNametag"] = luaFunction { shouldDisplay: Boolean ->
|
||||
self.displayNameTag = shouldDisplay
|
||||
}
|
||||
|
||||
callbacks["say"] = luaFunction { line: ByteString, tags: Table? ->
|
||||
var actualLine = line.decode()
|
||||
|
||||
if (tags != null) {
|
||||
actualLine = SBPattern.of(actualLine).resolveOrSkip({ tags[it]?.toString() })
|
||||
}
|
||||
|
||||
if (actualLine.isNotBlank()) {
|
||||
self.addChatMessage(actualLine.sbIntern())
|
||||
}
|
||||
|
||||
returnBuffer.setTo(actualLine.isNotBlank())
|
||||
}
|
||||
|
||||
callbacks["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table? ->
|
||||
var actualLine = line.decode()
|
||||
|
||||
if (tags != null) {
|
||||
actualLine = SBPattern.of(actualLine).resolveOrSkip({ tags[it]?.toString() })
|
||||
}
|
||||
|
||||
if (actualLine.isNotBlank()) {
|
||||
self.addChatMessage(actualLine.sbIntern(), portrait.decode().sbIntern())
|
||||
}
|
||||
|
||||
returnBuffer.setTo(actualLine.isNotBlank())
|
||||
}
|
||||
|
||||
callbacks["setDamageTeam"] = luaFunction { team: Any ->
|
||||
self.team.accept(Starbound.gson.fromJsonFast(toJsonFromLua(team), EntityDamageTeam::class.java))
|
||||
}
|
||||
|
||||
callbacks["setUniqueId"] = luaFunction { name: ByteString? ->
|
||||
self.uniqueID.accept(name?.decode()?.sbIntern())
|
||||
}
|
||||
|
||||
callbacks["setDamageBar"] = luaFunction { type: ByteString ->
|
||||
self.damageBarType = ActorEntity.DamageBarType.entries.valueOf(type.decode())
|
||||
}
|
||||
|
||||
callbacks["setInteractive"] = luaFunction { isInteractive: Boolean ->
|
||||
self.isInteractive = isInteractive
|
||||
}
|
||||
|
||||
callbacks["setAnimationParameter"] = luaFunction { name: ByteString, value: Any? ->
|
||||
self.scriptedAnimationParameters[name.decode().sbIntern()] = toJsonFromLua(value)
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.actor.ActorMovementModifiers
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableFrom
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.entities.ActorMovementController
|
||||
import ru.dbotthepony.kstarbound.world.entities.AnchorState
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import kotlin.math.PI
|
||||
|
||||
class MovementControllerBindings(val self: ActorMovementController) {
|
||||
fun init(lua: LuaEnvironment) {
|
||||
val callbacks = lua.newTable()
|
||||
lua.globals["mcontroller"] = callbacks
|
||||
|
||||
// pass-through
|
||||
callbacks["mass"] = luaFunction {
|
||||
returnBuffer.setTo(self.mass)
|
||||
}
|
||||
|
||||
callbacks["localBoundBox"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.computeLocalCollisionAABB()))
|
||||
}
|
||||
|
||||
callbacks["boundBox"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.computeLocalCollisionAABB()))
|
||||
}
|
||||
|
||||
callbacks["collisionBoundBox"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.computeGlobalCollisionAABB()))
|
||||
}
|
||||
|
||||
callbacks["collisionPoly"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.movementParameters.collisionPoly?.map({ it }, { it.firstOrNull() }) ?: Poly.EMPTY))
|
||||
}
|
||||
|
||||
callbacks["collisionPolies"] = luaFunction {
|
||||
returnBuffer.setTo(tableFrom((self.movementParameters.collisionPoly?.map({ listOf(it) }, { it }) ?: listOf(Poly.EMPTY)).map { from(it) }))
|
||||
}
|
||||
|
||||
callbacks["collisionBody"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.computeGlobalHitboxes().firstOrNull() ?: Poly.EMPTY))
|
||||
}
|
||||
|
||||
callbacks["collisionBodies"] = luaFunction {
|
||||
returnBuffer.setTo(tableFrom(self.computeGlobalHitboxes().map { from(it) }))
|
||||
}
|
||||
|
||||
callbacks["position"] = luaFunction { returnBuffer.setTo(tableOf(self.xPosition, self.yPosition)) }
|
||||
callbacks["xPosition"] = luaFunction { returnBuffer.setTo(self.xPosition) }
|
||||
callbacks["yPosition"] = luaFunction { returnBuffer.setTo(self.yPosition) }
|
||||
callbacks["velocity"] = luaFunction { returnBuffer.setTo(tableOf(self.xVelocity, self.yVelocity)) }
|
||||
callbacks["xVelocity"] = luaFunction { returnBuffer.setTo(self.xVelocity) }
|
||||
callbacks["yVelocity"] = luaFunction { returnBuffer.setTo(self.yVelocity) }
|
||||
callbacks["rotation"] = luaFunction { returnBuffer.setTo(self.rotation) }
|
||||
callbacks["isColliding"] = luaFunction { returnBuffer.setTo(self.isColliding) }
|
||||
callbacks["isNullColliding"] = luaFunction { returnBuffer.setTo(self.isCollidingWithNull) }
|
||||
callbacks["isCollisionStuck"] = luaFunction { returnBuffer.setTo(self.isCollisionStuck) }
|
||||
callbacks["stickingDirection"] = luaFunction { returnBuffer.setTo(self.stickingDirection) }
|
||||
callbacks["liquidPercentage"] = luaFunction { returnBuffer.setTo(self.liquidPercentage) }
|
||||
callbacks["liquidId"] = luaFunction { returnBuffer.setTo(self.liquid?.id ?: 0) }
|
||||
callbacks["liquidName"] = luaFunction { returnBuffer.setTo(self.liquid?.key.toByteString()) }
|
||||
callbacks["onGround"] = luaFunction { returnBuffer.setTo(self.isOnGround) }
|
||||
callbacks["zeroG"] = luaFunction { returnBuffer.setTo(self.isZeroGravity) }
|
||||
|
||||
callbacks["atWorldLimit"] = luaFunction { bottomOnly: Boolean ->
|
||||
returnBuffer.setTo(self.isAtWorldLimit(bottomOnly))
|
||||
}
|
||||
|
||||
callbacks["setAnchorState"] = luaFunction { anchor: Number, index: Number ->
|
||||
self.anchorState = AnchorState(anchor.toInt(), index.toInt())
|
||||
}
|
||||
|
||||
callbacks["resetAnchorState"] = luaFunction {
|
||||
self.anchorState = null
|
||||
}
|
||||
|
||||
callbacks["anchorState"] = luaFunction {
|
||||
val anchorState = self.anchorState
|
||||
|
||||
if (anchorState != null) {
|
||||
returnBuffer.setTo(anchorState.entityID, anchorState.positionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["setPosition"] = luaFunction { value: Table ->
|
||||
resetPathMove = true
|
||||
self.position = toVector2d(value)
|
||||
}
|
||||
|
||||
callbacks["setXPosition"] = luaFunction { value: Number ->
|
||||
resetPathMove = true
|
||||
self.xPosition = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["setYPosition"] = luaFunction { value: Number ->
|
||||
resetPathMove = true
|
||||
self.yPosition = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["translate"] = luaFunction { value: Table ->
|
||||
resetPathMove = true
|
||||
self.position += toVector2d(value)
|
||||
}
|
||||
|
||||
callbacks["setVelocity"] = luaFunction { value: Table ->
|
||||
resetPathMove = true
|
||||
self.velocity = toVector2d(value)
|
||||
}
|
||||
|
||||
callbacks["setXVelocity"] = luaFunction { value: Number ->
|
||||
resetPathMove = true
|
||||
self.xVelocity = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["setYVelocity"] = luaFunction { value: Number ->
|
||||
resetPathMove = true
|
||||
self.yVelocity = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["addMomentum"] = luaFunction { value: Table ->
|
||||
resetPathMove = true
|
||||
if (self.mass != 0.0) // let's not collapse into black hole
|
||||
self.velocity += toVector2d(value) / self.mass
|
||||
}
|
||||
|
||||
callbacks["setRotation"] = luaFunction { value: Number ->
|
||||
resetPathMove = true
|
||||
self.rotation = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["baseParameters"] = luaFunction { returnBuffer.setTo(from(Starbound.gson.toJsonTree(self.actorMovementParameters))) }
|
||||
callbacks["walking"] = luaFunction { returnBuffer.setTo(self.isWalking) }
|
||||
callbacks["running"] = luaFunction { returnBuffer.setTo(self.isRunning) }
|
||||
callbacks["movingDirection"] = luaFunction { returnBuffer.setTo(self.movingDirection.luaValue) }
|
||||
callbacks["facingDirection"] = luaFunction { returnBuffer.setTo(self.facingDirection.luaValue) }
|
||||
callbacks["crouching"] = luaFunction { returnBuffer.setTo(self.isCrouching) }
|
||||
callbacks["flying"] = luaFunction { returnBuffer.setTo(self.isFlying) }
|
||||
callbacks["falling"] = luaFunction { returnBuffer.setTo(self.isFalling) }
|
||||
callbacks["canJump"] = luaFunction { returnBuffer.setTo(self.canJump) }
|
||||
callbacks["jumping"] = luaFunction { returnBuffer.setTo(self.isJumping) }
|
||||
callbacks["groundMovement"] = luaFunction { returnBuffer.setTo(self.isGroundMovement) }
|
||||
callbacks["liquidMovement"] = luaFunction { returnBuffer.setTo(self.isLiquidMovement) }
|
||||
|
||||
// controls, stored locally, reset automatically or are persistent
|
||||
callbacks["controlRotation"] = luaFunction { value: Number ->
|
||||
controlRotationRate += value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["controlAcceleration"] = luaFunction { value: Table ->
|
||||
controlAcceleration += toVector2d(value)
|
||||
}
|
||||
|
||||
callbacks["controlForce"] = luaFunction { value: Table ->
|
||||
controlForce += toVector2d(value)
|
||||
}
|
||||
|
||||
callbacks["controlApproachVelocity"] = luaFunction { value: Table, rate: Number ->
|
||||
controlApproachVelocity = ActorMovementController.ApproachVelocityCommand(toVector2d(value), rate.toDouble())
|
||||
}
|
||||
|
||||
callbacks["controlApproachVelocityAlongAngle"] = luaFunction { angle: Number, targetVelocity: Number, maxControlForce: Number, positiveOnly: Boolean ->
|
||||
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(angle.toDouble(), targetVelocity.toDouble(), maxControlForce.toDouble(), positiveOnly)
|
||||
}
|
||||
|
||||
callbacks["controlApproachXVelocity"] = luaFunction { targetVelocity: Number, maxControlForce: Number ->
|
||||
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(0.0, targetVelocity.toDouble(), maxControlForce.toDouble(), false)
|
||||
}
|
||||
|
||||
callbacks["controlApproachYVelocity"] = luaFunction { targetVelocity: Number, maxControlForce: Number ->
|
||||
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(PI / 2.0, targetVelocity.toDouble(), maxControlForce.toDouble(), false)
|
||||
}
|
||||
|
||||
callbacks["controlParameters"] = luaFunction { data: Table ->
|
||||
controlParameters = controlParameters.merge(Starbound.gson.fromJsonFast(data.toJson(true), ActorMovementParameters::class.java))
|
||||
}
|
||||
|
||||
callbacks["controlModifiers"] = luaFunction { data: Table ->
|
||||
controlModifiers = controlModifiers.merge(Starbound.gson.fromJsonFast(data.toJson(true), ActorMovementModifiers::class.java))
|
||||
}
|
||||
|
||||
callbacks["controlMove"] = luaFunction { direction: Number, shouldRun: Boolean? ->
|
||||
// why?
|
||||
val new = Direction.valueOf(direction)
|
||||
|
||||
if (new != null) {
|
||||
controlMove = new
|
||||
controlShouldRun = shouldRun ?: false
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["controlFace"] = luaFunction { direction: Number ->
|
||||
// why?
|
||||
val new = Direction.valueOf(direction)
|
||||
|
||||
if (new != null)
|
||||
controlFace = new
|
||||
}
|
||||
|
||||
callbacks["controlDown"] = luaFunction { controlDown = true }
|
||||
callbacks["controlCrouch"] = luaFunction { controlCrouch = true }
|
||||
callbacks["controlJump"] = luaFunction { should: Boolean -> controlJump = should }
|
||||
callbacks["controlHoldJump"] = luaFunction { controlHoldJump = true }
|
||||
callbacks["controlFly"] = luaFunction { value: Table -> controlFly = toVector2d(value) }
|
||||
|
||||
callbacks["controlPathMove"] = luaFunction { position: Table, run: Boolean?, parameters: Table? ->
|
||||
if (pathMoveResult?.first == toVector2d(position)) {
|
||||
val pathMoveResult = pathMoveResult!!
|
||||
this@MovementControllerBindings.pathMoveResult = null
|
||||
returnBuffer.setTo(pathMoveResult.second)
|
||||
} else {
|
||||
pathMoveResult = null
|
||||
val result = self.pathMove(toVector2d(position), run == true, Starbound.gson.fromJsonFast(toJsonFromLua(parameters)))
|
||||
|
||||
if (result == null) {
|
||||
controlPathMove = toVector2d(position) to (run == true)
|
||||
}
|
||||
|
||||
returnBuffer.setTo(result?.second)
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["pathfinding"] = luaFunction {
|
||||
returnBuffer.setTo(self.pathController.isPathfinding)
|
||||
}
|
||||
|
||||
callbacks["autoClearControls"] = luaFunction {
|
||||
returnBuffer.setTo(autoClearControls)
|
||||
}
|
||||
|
||||
callbacks["setAutoClearControls"] = luaFunction { should: Boolean ->
|
||||
autoClearControls = should
|
||||
}
|
||||
|
||||
callbacks["clearControls"] = luaFunction { clearControls() }
|
||||
}
|
||||
|
||||
private var autoClearControls = true
|
||||
|
||||
private var controlRotationRate = 0.0
|
||||
private var controlAcceleration = Vector2d.ZERO
|
||||
private var controlForce = Vector2d.ZERO
|
||||
private var controlApproachVelocity: ActorMovementController.ApproachVelocityCommand? = null
|
||||
private var controlApproachVelocityAlongAngle: ActorMovementController.ApproachVelocityAngleCommand? = null
|
||||
private var controlParameters = ActorMovementParameters.EMPTY
|
||||
private var controlModifiers = ActorMovementModifiers.EMPTY
|
||||
|
||||
private var controlMove: Direction? = null
|
||||
private var controlFace: Direction? = null
|
||||
private var controlShouldRun: Boolean? = null
|
||||
|
||||
private var controlDown = false
|
||||
private var controlCrouch = false
|
||||
private var controlJump = false
|
||||
private var controlHoldJump = false
|
||||
private var controlFly: Vector2d? = null
|
||||
|
||||
private var resetPathMove = false
|
||||
|
||||
private var pathMoveResult: Pair<Vector2d, Boolean>? = null
|
||||
private var controlPathMove: Pair<Vector2d, Boolean>? = null
|
||||
|
||||
fun apply() {
|
||||
self.controlRotationRate += controlRotationRate
|
||||
self.controlAcceleration += controlAcceleration
|
||||
self.controlForce += controlForce
|
||||
|
||||
if (controlApproachVelocity != null) self.approachVelocities.add(controlApproachVelocity!!)
|
||||
if (controlApproachVelocityAlongAngle != null) self.approachVelocityAngles.add(controlApproachVelocityAlongAngle!!)
|
||||
self.controlActorMovementParameters = self.controlActorMovementParameters.merge(controlParameters)
|
||||
self.controlMovementModifiers = self.controlMovementModifiers.merge(controlModifiers)
|
||||
if (controlMove != null) self.controlMove = controlMove
|
||||
if (controlShouldRun != null) self.controlRun = controlShouldRun!!
|
||||
if (controlFace != null) self.controlFace = controlFace
|
||||
if (controlDown) self.controlDown = true
|
||||
if (controlCrouch) self.controlCrouch = true
|
||||
if (controlJump) self.controlJump = true
|
||||
if (controlHoldJump && !self.isOnGround) self.controlJump = true
|
||||
if (controlFly != null) self.controlFly = controlFly
|
||||
|
||||
// some action was taken that has priority over pathing, setting position or velocity
|
||||
if (resetPathMove)
|
||||
controlPathMove = null
|
||||
|
||||
if (controlPathMove != null && pathMoveResult == null)
|
||||
pathMoveResult = self.controlPathMove(controlPathMove!!.first, controlPathMove!!.second)
|
||||
}
|
||||
|
||||
fun clearControlsIfNeeded() {
|
||||
if (autoClearControls)
|
||||
clearControls()
|
||||
}
|
||||
|
||||
private fun clearControls() {
|
||||
controlRotationRate = 0.0
|
||||
controlAcceleration = Vector2d.ZERO
|
||||
controlForce = Vector2d.ZERO
|
||||
controlApproachVelocity = null
|
||||
controlApproachVelocityAlongAngle = null
|
||||
controlParameters = ActorMovementParameters.EMPTY
|
||||
controlModifiers = ActorMovementModifiers.EMPTY
|
||||
controlMove = null
|
||||
controlFace = null
|
||||
controlShouldRun = null
|
||||
controlDown = false
|
||||
controlCrouch = false
|
||||
controlJump = false
|
||||
controlHoldJump = false
|
||||
controlFly = null
|
||||
pathMoveResult = null
|
||||
controlPathMove = null
|
||||
resetPathMove = false
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.ByteString
|
||||
@ -37,6 +39,7 @@ import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableMapOf
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toLuaInteger
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
@ -125,7 +128,7 @@ private fun registryDef2(registry: Registry<*>): LuaFunction<Any?, *, *, *, *> {
|
||||
|
||||
if (def != null) {
|
||||
returnBuffer.setTo(newTable(0, 2).also {
|
||||
it["path"] = def.file?.computeFullPath()
|
||||
it["path"] = def.file?.computeFullPath().toByteString()
|
||||
it["config"] = from(def.json)
|
||||
})
|
||||
} else {
|
||||
@ -144,7 +147,7 @@ private val recipesForItem = luaFunction { name: ByteString ->
|
||||
val list = RecipeRegistry.output2recipes[name.decode()]
|
||||
|
||||
if (list == null) {
|
||||
returnBuffer.setTo(newTable())
|
||||
returnBuffer.setTo(tableOf())
|
||||
} else {
|
||||
returnBuffer.setTo(newTable(list.size, 0).also {
|
||||
for ((i, v) in list.withIndex()) {
|
||||
@ -192,17 +195,17 @@ private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIt
|
||||
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
||||
|
||||
if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) {
|
||||
context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it }))
|
||||
context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it }).toByteString())
|
||||
return
|
||||
}
|
||||
|
||||
if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) {
|
||||
context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it }))
|
||||
context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it }).toByteString())
|
||||
return
|
||||
}
|
||||
|
||||
// original engine parity
|
||||
context.returnBuffer.setTo("")
|
||||
context.returnBuffer.setTo("".toByteString())
|
||||
}
|
||||
|
||||
private fun materialFootstepSound(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||
@ -210,16 +213,16 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument
|
||||
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
|
||||
|
||||
if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
|
||||
context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it }))
|
||||
context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it }).toByteString())
|
||||
return
|
||||
}
|
||||
|
||||
if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
|
||||
context.returnBuffer.setTo(tile.value.footstepSound.map({ it.random() }, { it }))
|
||||
context.returnBuffer.setTo(tile.value.footstepSound.map({ it.random() }, { it }).toByteString())
|
||||
return
|
||||
}
|
||||
|
||||
context.returnBuffer.setTo(Globals.client.defaultFootstepSound.map({ it }, { it.random() }))
|
||||
context.returnBuffer.setTo(Globals.client.defaultFootstepSound.map({ it }, { it.random() }).toByteString())
|
||||
}
|
||||
|
||||
private val materialHealth = luaFunction { id: Any ->
|
||||
@ -227,7 +230,7 @@ private val materialHealth = luaFunction { id: Any ->
|
||||
}
|
||||
|
||||
private val liquidName = luaFunction { id: Any ->
|
||||
returnBuffer.setTo(lookupStrict(Registries.liquid, id).key)
|
||||
returnBuffer.setTo(lookupStrict(Registries.liquid, id).key.toByteString())
|
||||
}
|
||||
|
||||
private val liquidId = luaFunction { id: Any ->
|
||||
@ -235,11 +238,11 @@ private val liquidId = luaFunction { id: Any ->
|
||||
}
|
||||
|
||||
private val techType = luaFunction { id: Any ->
|
||||
returnBuffer.setTo(lookupStrict(Registries.techs, id).value.type)
|
||||
returnBuffer.setTo(lookupStrict(Registries.techs, id).value.type.toByteString())
|
||||
}
|
||||
|
||||
private val techConfig = luaFunction { id: Any ->
|
||||
returnBuffer.setTo(lookupStrict(Registries.techs, id).json)
|
||||
returnBuffer.setTo(from(lookupStrict(Registries.techs, id).json))
|
||||
}
|
||||
|
||||
private val jobject = luaFunction { returnBuffer.setTo(createJsonObject()) }
|
||||
@ -362,11 +365,11 @@ private val createBiome = luaFunction { name: ByteString, seed: Number, vertical
|
||||
}
|
||||
|
||||
private val treeStemDirectory = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(Registries.treeStemVariants[name.decode()]?.file?.computeDirectory(true) ?: "/")
|
||||
returnBuffer.setTo(Registries.treeStemVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString())
|
||||
}
|
||||
|
||||
private val treeFoliageDirectory = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(Registries.treeFoliageVariants[name.decode()]?.file?.computeDirectory(true) ?: "/")
|
||||
returnBuffer.setTo(Registries.treeFoliageVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString())
|
||||
}
|
||||
|
||||
private val itemConfig = luaFunction { descriptor: Any, level: Number?, seed: Number? ->
|
||||
@ -403,7 +406,7 @@ private val createItem = luaFunction { descriptor: Any, level: Number?, seed: Nu
|
||||
}
|
||||
|
||||
private val itemType = luaFunction { identifier: ByteString ->
|
||||
returnBuffer.setTo(ItemRegistry[identifier.decode()].type.jsonName)
|
||||
returnBuffer.setTo(ItemRegistry[identifier.decode()].type.jsonName.toByteString())
|
||||
}
|
||||
|
||||
private val itemTags = luaFunction { identifier: ByteString ->
|
||||
@ -414,6 +417,32 @@ private val itemHasTag = luaFunction { identifier: ByteString, tag: ByteString -
|
||||
returnBuffer.setTo(tag.decode() in ItemRegistry[identifier.decode()].itemTags)
|
||||
}
|
||||
|
||||
private val monsterSkillParameter = luaFunction { skillName: ByteString, configParameterName: ByteString ->
|
||||
val skill = Registries.monsterSkills[skillName.decode()]
|
||||
|
||||
if (skill != null) {
|
||||
returnBuffer.setTo(from(skill.value.config[configParameterName.decode()] ?: JsonNull.INSTANCE))
|
||||
}
|
||||
}
|
||||
|
||||
private val monsterParameters = luaFunction { monsterType: ByteString, seed: Number? ->
|
||||
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters))
|
||||
}
|
||||
|
||||
private val monsterMovementSettings = luaFunction { monsterType: ByteString, seed: Number? ->
|
||||
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters["movementSettings"] ?: JsonObject()))
|
||||
}
|
||||
|
||||
private val elementalResistance = luaFunction { damageKindName: ByteString ->
|
||||
returnBuffer.setTo(Globals.elementalTypes[Registries.damageKinds.getOrThrow(damageKindName.decode()).value.elementalType]!!.resistanceStat.toByteString())
|
||||
}
|
||||
|
||||
private val dungeonMetadata = luaFunction { dungeon: ByteString ->
|
||||
returnBuffer.setTo(from(Registries.dungeons.getOrThrow(dungeon.decode()).jsonObject["metadata"]))
|
||||
}
|
||||
|
||||
private val hasTech = registryDefExists(Registries.techs)
|
||||
|
||||
fun provideRootBindings(lua: LuaEnvironment) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["root"] = table
|
||||
@ -480,11 +509,11 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
||||
|
||||
table["createBiome"] = createBiome
|
||||
|
||||
table["monsterSkillParameter"] = luaStub("monsterSkillParameter")
|
||||
table["monsterParameters"] = luaStub("monsterParameters")
|
||||
table["monsterMovementSettings"] = luaStub("monsterMovementSettings")
|
||||
table["monsterSkillParameter"] = monsterSkillParameter
|
||||
table["monsterParameters"] = monsterParameters
|
||||
table["monsterMovementSettings"] = monsterMovementSettings
|
||||
|
||||
table["hasTech"] = registryDefExists(Registries.techs)
|
||||
table["hasTech"] = hasTech
|
||||
table["techType"] = techType
|
||||
table["techConfig"] = techConfig
|
||||
|
||||
@ -493,7 +522,6 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
||||
|
||||
table["collection"] = luaStub("collection")
|
||||
table["collectables"] = luaStub("collectables")
|
||||
table["elementalResistance"] = luaStub("elementalResistance")
|
||||
table["dungeonMetadata"] = luaStub("dungeonMetadata")
|
||||
table["behavior"] = luaStub("behavior")
|
||||
table["elementalResistance"] = elementalResistance
|
||||
table["dungeonMetadata"] = dungeonMetadata
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||
import ru.dbotthepony.kstarbound.lua.toAABBi
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
@ -234,7 +235,7 @@ fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvi
|
||||
}
|
||||
|
||||
callbacks["fidelity"] = luaFunction {
|
||||
returnBuffer.setTo("high")
|
||||
returnBuffer.setTo("high".toByteString())
|
||||
}
|
||||
|
||||
callbacks["setSkyTime"] = luaFunction { newTime: Number ->
|
||||
@ -247,7 +248,7 @@ fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvi
|
||||
}
|
||||
|
||||
callbacks["setUniverseFlag"] = luaFunction { flag: ByteString ->
|
||||
returnBuffer.setTo(self.server.addUniverseFlag(flag.decode()))
|
||||
returnBuffer.setTo(self.server.addUniverseFlag(flag.decode().sbIntern()))
|
||||
}
|
||||
|
||||
callbacks["unsetUniverseFlag"] = luaFunction { flag: ByteString ->
|
||||
|
@ -0,0 +1,251 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.DamageData
|
||||
import ru.dbotthepony.kstarbound.defs.EphemeralStatusEffect
|
||||
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||
import ru.dbotthepony.kstarbound.world.entities.StatusController
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
private object PersistentStatusEffectToken : TypeToken<PersistentStatusEffect>()
|
||||
private object CPersistentStatusEffectToken : TypeToken<ArrayList<PersistentStatusEffect>>()
|
||||
|
||||
fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment) {
|
||||
val callbacks = lua.newTable()
|
||||
lua.globals["status"] = callbacks
|
||||
|
||||
callbacks["statusProperty"] = luaFunction { key: ByteString, ifMissing: Any? ->
|
||||
val get = self.getProperty(key.decode())
|
||||
|
||||
if (get == null) {
|
||||
returnBuffer.setTo(ifMissing)
|
||||
} else {
|
||||
returnBuffer.setTo(from(get))
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["setStatusProperty"] = luaFunction { key: ByteString, value: Any? ->
|
||||
self.setProperty(key.decode().sbIntern(), toJsonFromLua(value))
|
||||
}
|
||||
|
||||
callbacks["stat"] = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(self.liveStats[name.decode()]?.effectiveModifiedValue ?: 0.0)
|
||||
}
|
||||
|
||||
callbacks["statPositive"] = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(self.statPositive(name.decode()))
|
||||
}
|
||||
|
||||
callbacks["resourceNames"] = luaFunction {
|
||||
returnBuffer.setTo(tableOf(self.resources.keys.toTypedArray()))
|
||||
}
|
||||
|
||||
callbacks["isResource"] = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(name.decode() in self.resources.keys)
|
||||
}
|
||||
|
||||
callbacks["resource"] = luaFunction { name: ByteString ->
|
||||
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(0.0)
|
||||
returnBuffer.setTo(resource.value)
|
||||
}
|
||||
|
||||
callbacks["resourcePositive"] = luaFunction { name: ByteString ->
|
||||
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||
returnBuffer.setTo(resource.value > 0.0)
|
||||
}
|
||||
|
||||
callbacks["setResource"] = luaFunction { name: ByteString, value: Number ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.value = value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["modifyResource"] = luaFunction { name: ByteString, value: Number ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.value += value.toDouble()
|
||||
}
|
||||
|
||||
callbacks["giveResource"] = luaFunction { name: ByteString, value: Number ->
|
||||
// while other functions throw an exception if resource does not exist
|
||||
// this one returns 0
|
||||
// Consistency is my first, second, and last name
|
||||
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(0.0)
|
||||
returnBuffer.setTo(resource.give(value.toDouble()))
|
||||
}
|
||||
|
||||
callbacks["consumeResource"] = luaFunction { name: ByteString, value: Number ->
|
||||
// while other functions throw an exception if resource does not exist
|
||||
// this one returns 0
|
||||
// Consistency is my first, second, and last name
|
||||
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||
returnBuffer.setTo(resource.consume(value.toDouble()))
|
||||
}
|
||||
|
||||
callbacks["overConsumeResource"] = luaFunction { name: ByteString, value: Number ->
|
||||
// while other functions throw an exception if resource does not exist
|
||||
// this one returns 0
|
||||
// Consistency is my first, second, and last name
|
||||
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false)
|
||||
returnBuffer.setTo(resource.consume(value.toDouble(), allowOverdraw = true))
|
||||
}
|
||||
|
||||
callbacks["resourceLocked"] = luaFunction { name: ByteString ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
returnBuffer.setTo(resource.isLocked)
|
||||
}
|
||||
|
||||
callbacks["setResourceLocked"] = luaFunction { name: ByteString, isLocked: Boolean ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.isLocked = isLocked
|
||||
}
|
||||
|
||||
callbacks["resetResource"] = luaFunction { name: ByteString, isLocked: Boolean ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.reset()
|
||||
}
|
||||
|
||||
callbacks["resetAllResources"] = luaFunction { name: ByteString, isLocked: Boolean ->
|
||||
self.resetResources()
|
||||
}
|
||||
|
||||
callbacks["resourceMax"] = luaFunction { name: ByteString ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
returnBuffer.setTo(resource.maxValue)
|
||||
}
|
||||
|
||||
callbacks["resourcePercentage"] = luaFunction { name: ByteString ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
returnBuffer.setTo(resource.percentage)
|
||||
}
|
||||
|
||||
callbacks["setResourcePercentage"] = luaFunction { name: ByteString, value: Number ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.setAsPercentage(value.toDouble())
|
||||
}
|
||||
|
||||
callbacks["modifyResourcePercentage"] = luaFunction { name: ByteString, value: Number ->
|
||||
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
|
||||
resource.modifyPercentage(value.toDouble())
|
||||
}
|
||||
|
||||
callbacks["getPersistentEffects"] = luaFunction { category: ByteString ->
|
||||
returnBuffer.setTo(tableOf(self.getPersistentEffects(category.decode()).map { it.map({ from(Starbound.gson.toJsonTree(it)) }, { it }) }))
|
||||
}
|
||||
|
||||
callbacks["addPersistentEffect"] = luaFunction { category: ByteString, effect: Any ->
|
||||
self.addPersistentEffect(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), PersistentStatusEffectToken))
|
||||
}
|
||||
|
||||
callbacks["addPersistentEffects"] = luaFunction { category: ByteString, effect: Any ->
|
||||
self.addPersistentEffects(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), CPersistentStatusEffectToken))
|
||||
}
|
||||
|
||||
callbacks["setPersistentEffects"] = luaFunction { category: ByteString, effect: Any ->
|
||||
self.setPersistentEffects(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), CPersistentStatusEffectToken))
|
||||
}
|
||||
|
||||
callbacks["clearPersistentEffects"] = luaFunction { category: ByteString ->
|
||||
self.removePersistentEffects(category.decode())
|
||||
}
|
||||
|
||||
callbacks["clearAllPersistentEffects"] = luaFunction {
|
||||
self.removeAllPersistentEffects()
|
||||
}
|
||||
|
||||
callbacks["addEphemeralEffect"] = luaFunction { name: ByteString, duration: Number?, source: Number? ->
|
||||
self.addEphemeralEffect(EphemeralStatusEffect(Registries.statusEffects.ref(name.decode().sbIntern()), duration?.toDouble()), source?.toInt())
|
||||
}
|
||||
|
||||
callbacks["addEphemeralEffects"] = luaFunction { effects: Table, source: Number? ->
|
||||
for ((_, effect) in effects) {
|
||||
self.addEphemeralEffect(Starbound.gson.fromJsonFast(toJsonFromLua(effect), EphemeralStatusEffect::class.java), source?.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["removeEphemeralEffect"] = luaFunction { name: ByteString ->
|
||||
self.removeEphemeralEffect(name.decode())
|
||||
}
|
||||
|
||||
callbacks["clearEphemeralEffects"] = luaFunction {
|
||||
self.removeEphemeralEffects()
|
||||
}
|
||||
|
||||
callbacks["damageTakenSince"] = luaFunction { since: Number? ->
|
||||
val (list, newSince) = self.recentDamageReceived(since?.toLong() ?: 0L)
|
||||
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince)
|
||||
}
|
||||
|
||||
callbacks["inflictedHitsSince"] = luaFunction { since: Number? ->
|
||||
val (list, newSince) = self.recentHitsDealt(since?.toLong() ?: 0L)
|
||||
|
||||
returnBuffer.setTo(tableOf(*list.map { p ->
|
||||
from(Starbound.gson.toJsonTree(p.second).also { it as JsonObject; it["targetEntityId"] = p.first })
|
||||
}.toTypedArray()), newSince)
|
||||
}
|
||||
|
||||
callbacks["inflictedDamageSince"] = luaFunction { since: Number? ->
|
||||
val (list, newSince) = self.recentDamageDealt(since?.toLong() ?: 0L)
|
||||
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince)
|
||||
}
|
||||
|
||||
callbacks["activeUniqueStatusEffectSummary"] = luaFunction {
|
||||
returnBuffer.setTo(tableOf(*self.activeUniqueStatusEffectSummary().map { tableOf(it.first.key, it.second) }.toTypedArray()))
|
||||
}
|
||||
|
||||
callbacks["uniqueStatusEffectActive"] = luaFunction { name: ByteString ->
|
||||
returnBuffer.setTo(self.uniqueStatusEffectActive(name.decode()))
|
||||
}
|
||||
|
||||
callbacks["primaryDirectives"] = luaFunction {
|
||||
returnBuffer.setTo(self.primaryDirectives.toByteString())
|
||||
}
|
||||
|
||||
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString? ->
|
||||
self.primaryDirectives = directives?.decode()?.sbIntern() ?: ""
|
||||
}
|
||||
|
||||
callbacks["applySelfDamageRequest"] = luaFunction { damage: Table ->
|
||||
self.inflictSelfDamage(Starbound.gson.fromJsonFast(toJsonFromLua(damage), DamageData::class.java))
|
||||
}
|
||||
|
||||
callbacks["appliesEnvironmentStatusEffects"] = luaFunction {
|
||||
returnBuffer.setTo(self.appliesEnvironmentStatusEffects)
|
||||
}
|
||||
|
||||
callbacks["appliesWeatherStatusEffects"] = luaFunction {
|
||||
returnBuffer.setTo(self.appliesWeatherStatusEffects)
|
||||
}
|
||||
|
||||
callbacks["minimumLiquidStatusEffectPercentage"] = luaFunction {
|
||||
returnBuffer.setTo(self.minimumLiquidStatusEffectPercentage)
|
||||
}
|
||||
|
||||
callbacks["setAppliesEnvironmentStatusEffects"] = luaFunction { should: Boolean ->
|
||||
self.appliesEnvironmentStatusEffects = should
|
||||
}
|
||||
|
||||
callbacks["setAppliesWeatherStatusEffects"] = luaFunction { should: Boolean ->
|
||||
self.appliesWeatherStatusEffects = should
|
||||
}
|
||||
|
||||
callbacks["setMinimumLiquidStatusEffectPercentage"] = luaFunction { value: Number ->
|
||||
self.minimumLiquidStatusEffectPercentage = value.toDouble()
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
@ -14,7 +17,9 @@ import ru.dbotthepony.kstarbound.lua.luaFunctionArray
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
||||
@ -67,7 +72,7 @@ private val interpolateSinEase = luaFunctionArray { args ->
|
||||
}
|
||||
|
||||
private val replaceTags = luaFunction { string: ByteString, tags: Table ->
|
||||
returnBuffer.setTo(SBPattern.of(string.toString()).resolveOrSkip({ tags[it]?.toString() }))
|
||||
returnBuffer.setTo(SBPattern.of(string.toString()).resolveOrSkip({ tags[it]?.toString() }).toByteString())
|
||||
}
|
||||
|
||||
private val makePerlinSource = luaFunction { settings: Table ->
|
||||
@ -94,15 +99,16 @@ private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
|
||||
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
|
||||
}
|
||||
|
||||
fun provideUtilityBindings(
|
||||
lua: LuaEnvironment,
|
||||
random: RandomGenerator = random()
|
||||
) {
|
||||
private val mergeJson = luaFunction { a: Any?, b: Any? ->
|
||||
returnBuffer.setTo(from(ru.dbotthepony.kstarbound.json.mergeJson(toJsonFromLua(a), toJsonFromLua(b))))
|
||||
}
|
||||
|
||||
fun provideUtilityBindings(lua: LuaEnvironment) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["sb"] = table
|
||||
|
||||
table["makeUuid"] = luaFunction {
|
||||
returnBuffer.setTo(UUID(random.nextLong(), random.nextLong()).toStarboundString())
|
||||
returnBuffer.setTo(UUID(lua.random.nextLong(), lua.random.nextLong()).toStarboundString().toByteString())
|
||||
}
|
||||
|
||||
table["logInfo"] = logInfo
|
||||
@ -112,7 +118,7 @@ fun provideUtilityBindings(
|
||||
table["nrand"] = luaFunctionN("nrand") { args ->
|
||||
val stdev = args.nextOptionalFloat() ?: 1.0
|
||||
val mean = args.nextOptionalFloat() ?: 0.0
|
||||
random.nextNormalDouble(stdev, mean)
|
||||
lua.random.nextNormalDouble(stdev, mean)
|
||||
}
|
||||
|
||||
table["print"] = lua.globals["tostring"]
|
||||
@ -120,7 +126,7 @@ fun provideUtilityBindings(
|
||||
table["interpolateSinEase"] = interpolateSinEase
|
||||
table["replaceTags"] = replaceTags
|
||||
table["makeRandomSource"] = luaFunction { seed: Long? ->
|
||||
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: random.nextLong())))
|
||||
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: lua.random.nextLong())))
|
||||
}
|
||||
|
||||
table["makePerlinSource"] = makePerlinSource
|
||||
@ -132,4 +138,21 @@ fun provideUtilityBindings(
|
||||
table["staticRandomDoubleRange"] = staticRandomDoubleRange
|
||||
table["staticRandomI32Range"] = staticRandomI32Range
|
||||
table["staticRandomI64Range"] = staticRandomI32Range
|
||||
|
||||
table["jsonMerge"] = mergeJson
|
||||
}
|
||||
|
||||
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) {
|
||||
val config = lua.newTable()
|
||||
lua.globals["config"] = config
|
||||
config["getParameter"] = createConfigBinding(lookup)
|
||||
}
|
||||
|
||||
fun createConfigBinding(lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) = luaFunction { name: ByteString, default: Any? ->
|
||||
val get = lookup(this, JsonPath.query(name.decode()), default)
|
||||
|
||||
if (get is JsonElement)
|
||||
returnBuffer.setTo(from(get))
|
||||
else
|
||||
returnBuffer.setTo(get)
|
||||
}
|
||||
|
@ -12,12 +12,15 @@ import org.classdump.luna.runtime.LuaFunction
|
||||
import ru.dbotthepony.kommons.collect.map
|
||||
import ru.dbotthepony.kommons.collect.toList
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
@ -33,6 +36,7 @@ import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toLine2d
|
||||
@ -41,9 +45,11 @@ import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.lua.unpackAsArray
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.util.CarriedExecutor
|
||||
import ru.dbotthepony.kstarbound.util.GameTimer
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.shuffle
|
||||
@ -57,6 +63,7 @@ import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
|
||||
import ru.dbotthepony.kstarbound.world.castRay
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.PathFinder
|
||||
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
@ -82,7 +89,7 @@ private fun ExecutionContext.resolvePolyCollision(self: World<*, *>, originalPol
|
||||
|
||||
val tiles = ObjectArrayList<Entry>()
|
||||
|
||||
for (tile in self.queryTileCollisions(poly.aabb.enlarge(maximumCorrection + 1.0, maximumCorrection + 1.0))) {
|
||||
for (tile in self.chunkMap.queryTileCollisions(poly.aabb.enlarge(maximumCorrection + 1.0, maximumCorrection + 1.0))) {
|
||||
if (tile.type in collisions) {
|
||||
tiles.add(Entry(tile.poly, tile.poly.centre))
|
||||
}
|
||||
@ -185,6 +192,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["distance"] = luaFunction { arg1: Table, arg2: Table ->
|
||||
returnBuffer.setTo(from(self.geometry.diff(toVector2d(arg1), toVector2d(arg2))))
|
||||
}
|
||||
|
||||
callbacks["polyContains"] = luaFunction { poly: Table, position: Table ->
|
||||
returnBuffer.setTo(self.geometry.polyContains(toPoly(poly), toVector2d(position)))
|
||||
}
|
||||
@ -217,19 +228,19 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
|
||||
callbacks["rectCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||
if (collisions == null) {
|
||||
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type.isSolidCollision }).findAny().isPresent)
|
||||
returnBuffer.setTo(self.chunkMap.collide(toPoly(rect), Predicate { it.type.isSolidCollision }).findAny().isPresent)
|
||||
} else {
|
||||
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||
returnBuffer.setTo(self.chunkMap.collide(toPoly(rect), Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["pointCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||
if (collisions == null) {
|
||||
returnBuffer.setTo(self.collide(toVector2d(rect), Predicate { it.type.isSolidCollision }))
|
||||
returnBuffer.setTo(self.chunkMap.collide(toVector2d(rect), Predicate { it.type.isSolidCollision }))
|
||||
} else {
|
||||
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||
returnBuffer.setTo(self.collide(toVector2d(rect), Predicate { it.type in actualCollisions }))
|
||||
returnBuffer.setTo(self.chunkMap.collide(toVector2d(rect), Predicate { it.type in actualCollisions }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,17 +281,18 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
|
||||
callbacks["rectTileCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||
if (collisions == null) {
|
||||
returnBuffer.setTo(self.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision }))
|
||||
returnBuffer.setTo(self.chunkMap.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision }))
|
||||
} else {
|
||||
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||
returnBuffer.setTo(self.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind in actualCollisions }))
|
||||
val a = self.chunkMap.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind in actualCollisions })
|
||||
returnBuffer.setTo(a)
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["lineCollision"] = luaFunction { pos0: Table, pos1: Table, collisions: Table? ->
|
||||
val actualCollisions = if (collisions == null) CollisionType.SOLID else EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||
|
||||
val result = self.collide(Line2d(toVector2d(pos0), toVector2d(pos1))) { it.type in actualCollisions }
|
||||
val result = self.chunkMap.collide(Line2d(toVector2d(pos0), toVector2d(pos1))) { it.type in actualCollisions }
|
||||
|
||||
if (result == null) {
|
||||
returnBuffer.setTo()
|
||||
@ -291,10 +303,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
|
||||
callbacks["polyCollision"] = luaFunction { rect: Table, translate: Table?, collisions: Table? ->
|
||||
if (collisions == null) {
|
||||
returnBuffer.setTo(self.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type.isSolidCollision }).findAny().isPresent)
|
||||
returnBuffer.setTo(self.chunkMap.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type.isSolidCollision }).findAny().isPresent)
|
||||
} else {
|
||||
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||
returnBuffer.setTo(self.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||
returnBuffer.setTo(self.chunkMap.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,7 +473,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
|
||||
callbacks["spawnMonster"] = luaFunction {
|
||||
// TODO
|
||||
returnBuffer.setTo(0)
|
||||
returnBuffer.setTo(0L)
|
||||
}
|
||||
callbacks["spawnNpc"] = luaStub("spawnNpc")
|
||||
callbacks["spawnStagehand"] = luaStub("spawnStagehand")
|
||||
@ -483,7 +495,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
}
|
||||
|
||||
callbacks["liquidAt"] = luaFunction { posOrRect: Table ->
|
||||
if (posOrRect[1L] is Number) {
|
||||
if (posOrRect[3L] !is Number) {
|
||||
val cell = self.getCell(toVector2i(posOrRect))
|
||||
|
||||
if (cell.liquid.state.isNotEmptyLiquid) {
|
||||
@ -499,7 +511,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
}
|
||||
|
||||
callbacks["liquidNameAt"] = luaFunction { posOrRect: Table ->
|
||||
if (posOrRect[1L] is Number) {
|
||||
if (posOrRect[3L] !is Number) {
|
||||
val cell = self.getCell(toVector2i(posOrRect))
|
||||
|
||||
if (cell.liquid.state.isNotEmptyLiquid) {
|
||||
@ -515,11 +527,11 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
}
|
||||
|
||||
callbacks["gravity"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.gravityAt(toVector2d(pos)).y)
|
||||
returnBuffer.setTo(self.chunkMap.gravityAt(toVector2d(pos)).y)
|
||||
}
|
||||
|
||||
callbacks["gravityVector"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(from(self.gravityAt(toVector2d(pos))))
|
||||
returnBuffer.setTo(from(self.chunkMap.gravityAt(toVector2d(pos))))
|
||||
}
|
||||
|
||||
callbacks["spawnLiquidPromise"] = luaFunction { pos: Table, liquid: Any, quantity: Number ->
|
||||
@ -559,10 +571,32 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
|
||||
callbacks["isTileProtected"] = luaFunction { pos: Table -> returnBuffer.setTo(self.isDungeonIDProtected(self.getCell(toVector2i(pos)).dungeonId)) }
|
||||
|
||||
callbacks["findPlatformerPath"] = luaStub("findPlatformerPath")
|
||||
callbacks["platformerPathStart"] = luaStub("platformerPathStart")
|
||||
callbacks["findPlatformerPath"] = luaFunction { start: Table, end: Table, actorParams: Table, searchParams: Table ->
|
||||
LOGGER.warn("world.findPlatformerPath() was called, this will cause world lag. Consider switching to world.platformerPathStart(), since it is multithreaded in new engine.")
|
||||
|
||||
callbacks["type"] = luaFunction { returnBuffer.setTo(self.template.worldParameters?.typeName ?: "unknown") }
|
||||
val finder = PathFinder(
|
||||
self,
|
||||
toVector2d(start),
|
||||
toVector2d(end),
|
||||
Starbound.gson.fromJsonFast(toJsonFromLua(actorParams), ActorMovementParameters::class.java),
|
||||
Starbound.gson.fromJsonFast(toJsonFromLua(searchParams), PathFinder.Parameters::class.java))
|
||||
|
||||
finder.run(Int.MAX_VALUE)
|
||||
returnBuffer.setTo(LuaPathFinder.convertPath(this, finder.result.orNull()))
|
||||
}
|
||||
|
||||
val pacer = CarriedExecutor(Starbound.EXECUTOR)
|
||||
|
||||
callbacks["platformerPathStart"] = luaFunction { start: Table, end: Table, actorParams: Table, searchParams: Table ->
|
||||
returnBuffer.setTo(LuaPathFinder(pacer, PathFinder(
|
||||
self,
|
||||
toVector2d(start),
|
||||
toVector2d(end),
|
||||
Starbound.gson.fromJsonFast(toJsonFromLua(actorParams), ActorMovementParameters::class.java),
|
||||
Starbound.gson.fromJsonFast(toJsonFromLua(searchParams), PathFinder.Parameters::class.java))))
|
||||
}
|
||||
|
||||
callbacks["type"] = luaFunction { returnBuffer.setTo(self.template.worldParameters?.typeName.toByteString() ?: "unknown".toByteString()) }
|
||||
callbacks["size"] = luaFunction { returnBuffer.setTo(from(self.geometry.size)) }
|
||||
callbacks["inSurfaceLayer"] = luaFunction { pos: Table -> returnBuffer.setTo(self.template.isSurfaceLayer(toVector2i(pos))) }
|
||||
callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel) }
|
||||
@ -610,4 +644,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
if (self is ServerWorld) {
|
||||
provideServerWorldBindings(self, callbacks, lua)
|
||||
}
|
||||
|
||||
// TODO
|
||||
callbacks["debugPoint"] = luaFunction { }
|
||||
callbacks["debugLine"] = luaFunction { }
|
||||
callbacks["debugPoly"] = luaFunction { }
|
||||
callbacks["debugText"] = luaFunction { }
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableMapOf
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toLine2d
|
||||
import ru.dbotthepony.kstarbound.lua.toPoly
|
||||
@ -345,12 +346,12 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
|
||||
|
||||
callbacks["entitySpecies"] = luaFunction { id: Number ->
|
||||
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||
returnBuffer.setTo(entity.species)
|
||||
returnBuffer.setTo(entity.species.toByteString())
|
||||
}
|
||||
|
||||
callbacks["entityGender"] = luaFunction { id: Number ->
|
||||
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||
returnBuffer.setTo(entity.gender.jsonName)
|
||||
returnBuffer.setTo(entity.gender.jsonName.toByteString())
|
||||
}
|
||||
|
||||
callbacks["entityName"] = luaFunction { id: Number ->
|
||||
@ -358,8 +359,8 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
|
||||
|
||||
// TODO
|
||||
when (entity) {
|
||||
is ActorEntity -> returnBuffer.setTo(entity.name)
|
||||
is WorldObject -> returnBuffer.setTo(entity.config.key)
|
||||
is ActorEntity -> returnBuffer.setTo(entity.name.toByteString())
|
||||
is WorldObject -> returnBuffer.setTo(entity.config.key.toByteString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,8 +384,8 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
|
||||
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||
|
||||
when (val gethand = hand.decode().lowercase()) {
|
||||
"primary" -> returnBuffer.setTo(entity.primaryHandItem.entry.nameOrNull)
|
||||
"alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.entry.nameOrNull)
|
||||
"primary" -> returnBuffer.setTo(entity.primaryHandItem.entry.nameOrNull.toByteString())
|
||||
"alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.entry.nameOrNull.toByteString())
|
||||
else -> throw LuaRuntimeException("Unknown tool hand $gethand")
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kommons.collect.map
|
||||
import ru.dbotthepony.kommons.collect.toList
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||
@ -26,6 +27,7 @@ import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableFrom
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||
@ -107,7 +109,7 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
|
||||
callbacks["windLevel"] = luaStub("windLevel")
|
||||
|
||||
callbacks["breathable"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.isBreathable(toVector2i(pos)))
|
||||
returnBuffer.setTo(self.chunkMap.isBreathable(toVector2i(pos)))
|
||||
}
|
||||
|
||||
callbacks["underground"] = luaFunction { pos: Table ->
|
||||
@ -123,7 +125,7 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
|
||||
} else if (tile.material.isEmptyTile) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
returnBuffer.setTo(tile.material.key)
|
||||
returnBuffer.setTo(tile.material.key.toByteString())
|
||||
}
|
||||
|
||||
}
|
||||
@ -132,32 +134,32 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
|
||||
if (tile.modifier.isNotEmptyModifier) {
|
||||
returnBuffer.setTo(tile.modifier.key)
|
||||
returnBuffer.setTo(tile.modifier.key.toByteString())
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["materialHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.hueShift)
|
||||
returnBuffer.setTo(tile.hueShift.toDouble())
|
||||
}
|
||||
|
||||
callbacks["modHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.modifierHueShift)
|
||||
returnBuffer.setTo(tile.modifierHueShift.toDouble())
|
||||
}
|
||||
|
||||
callbacks["materialColor"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.color.ordinal)
|
||||
returnBuffer.setTo(tile.color.ordinal.toLong())
|
||||
}
|
||||
|
||||
callbacks["materialColorName"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.color.jsonName)
|
||||
returnBuffer.setTo(tile.color.jsonName.toByteString())
|
||||
}
|
||||
|
||||
callbacks["setMaterialColor"] = luaFunction { pos: Table, layer: ByteString, color: Any ->
|
||||
@ -177,11 +179,11 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
|
||||
}
|
||||
|
||||
callbacks["oceanLevel"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.template.cellInfo(toVector2i(pos)).oceanLiquidLevel)
|
||||
returnBuffer.setTo(self.template.cellInfo(toVector2i(pos)).oceanLiquidLevel.toLong())
|
||||
}
|
||||
|
||||
callbacks["environmentStatusEffects"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(tableFrom(self.environmentStatusEffects(toVector2i(pos))))
|
||||
returnBuffer.setTo(tableFrom(self.environmentStatusEffects(toVector2i(pos)).map { from(Starbound.gson.toJsonTree(it)) }))
|
||||
}
|
||||
|
||||
callbacks["damageTiles"] = luaFunctionN("damageTiles") {
|
||||
|
@ -21,12 +21,14 @@ import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.toColor
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
|
||||
@ -48,12 +50,12 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["object"] = table
|
||||
|
||||
table["name"] = luaFunction { returnBuffer.setTo(self.config.key) }
|
||||
table["name"] = luaFunction { returnBuffer.setTo(self.config.key.toByteString()) }
|
||||
table["direction"] = luaFunction { returnBuffer.setTo(self.direction.luaValue) }
|
||||
table["position"] = luaFunction { returnBuffer.setTo(from(self.tilePosition)) }
|
||||
table["setInteractive"] = luaFunction { interactive: Boolean -> self.isInteractive = interactive }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get()) }
|
||||
table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(id?.decode()) }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) }
|
||||
table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(id?.decode()?.sbIntern()) }
|
||||
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
|
||||
|
||||
// original engine parity, it returns occupied spaces in local coordinates
|
||||
@ -127,8 +129,8 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
||||
returnBuffer.setTo(from(self.lightSourceColor))
|
||||
}
|
||||
|
||||
table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size) }
|
||||
table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size) }
|
||||
table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size.toLong()) }
|
||||
table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size.toLong()) }
|
||||
|
||||
table["getInputNodePosition"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(from(self.inputNodes[index.toInt()].position))
|
||||
|
@ -0,0 +1,126 @@
|
||||
package ru.dbotthepony.kstarbound.lua.userdata
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition
|
||||
import ru.dbotthepony.kstarbound.fromJsonFast
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableMapOf
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
|
||||
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() {
|
||||
val blackboard get() = tree.blackboard
|
||||
val lua get() = blackboard.lua
|
||||
val functions = HashMap<String, LuaFunction<*, *, *, *, *>>()
|
||||
|
||||
init {
|
||||
lua.attach(tree.scripts)
|
||||
|
||||
for (name in tree.functions) {
|
||||
val get = lua.globals[name]
|
||||
|
||||
if (get == null) {
|
||||
throw LuaRuntimeException("No such function for behavior: $name")
|
||||
} else if (get !is LuaFunction<*, *, *, *, *>) {
|
||||
throw LuaRuntimeException("Not a Lua function for behavior: $name")
|
||||
} else {
|
||||
this.functions[name] = get
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun run(delta: Double): AbstractBehaviorNode.Status {
|
||||
val ephemerals = blackboard.takeEphemerals()
|
||||
val status = tree.runAndReset(delta, this)
|
||||
blackboard.clearEphemerals(ephemerals)
|
||||
return status
|
||||
}
|
||||
|
||||
override fun getMetatable(): Table {
|
||||
return Companion.metatable
|
||||
}
|
||||
|
||||
override fun setMetatable(mt: Table?): Table {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getUserValue(): BehaviorState {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setUserValue(value: BehaviorState?): BehaviorState? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// required for Lua code
|
||||
// in original engine, passes directly 32/64 bit pointer of *Node struct
|
||||
val NODE_GARBAGE_INDEX = AtomicLong()
|
||||
|
||||
private fun __index(): Table {
|
||||
return metatable
|
||||
}
|
||||
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||
.add("run", luaFunction { self: BehaviorState, delta: Number ->
|
||||
returnBuffer.setTo(self.run(delta.toDouble()))
|
||||
})
|
||||
.add("clear", luaFunction { self: BehaviorState ->
|
||||
self.tree.reset()
|
||||
})
|
||||
.add("blackboard", luaFunction { self: BehaviorState ->
|
||||
returnBuffer.setTo(self.blackboard)
|
||||
})
|
||||
.build()
|
||||
|
||||
fun provideBindings(lua: LuaEnvironment) {
|
||||
lua.globals["behavior"] = lua.tableMapOf(
|
||||
"behavior" to luaFunction { config: Any, parameters: Table, _: Any?, blackboard: Blackboard? ->
|
||||
val tree: BehaviorTree
|
||||
|
||||
if (config is ByteString) {
|
||||
val base = Registries.behavior.getOrThrow(config.decode())
|
||||
|
||||
if (!parameters.iterator().hasNext()) {
|
||||
tree = BehaviorTree(blackboard ?: Blackboard(lua), base.value)
|
||||
} else {
|
||||
tree = BehaviorTree(blackboard ?: Blackboard(lua), Starbound.gson.fromJsonFast(base.json.deepCopy().also {
|
||||
it as JsonObject
|
||||
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
|
||||
}, BehaviorDefinition::class.java))
|
||||
}
|
||||
} else {
|
||||
val cast = (config as Table).toJson(true) as JsonObject
|
||||
|
||||
if (parameters.iterator().hasNext())
|
||||
cast["parameters"] = mergeJson(cast["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
|
||||
|
||||
tree = BehaviorTree(blackboard ?: Blackboard(lua), Starbound.gson.fromJsonFast(cast, BehaviorDefinition::class.java))
|
||||
}
|
||||
|
||||
returnBuffer.setTo(BehaviorState(tree))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.userdata
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -32,7 +33,12 @@ class LuaFuture(val future: CompletableFuture<out Any?>, val isLocal: Boolean) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun __index(): Table {
|
||||
return metadata
|
||||
}
|
||||
|
||||
private val metadata = ImmutableTable.Builder()
|
||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||
.add("finished", luaFunction { self: LuaFuture ->
|
||||
returnBuffer.setTo(self.future.isDone)
|
||||
})
|
||||
|
@ -0,0 +1,101 @@
|
||||
package ru.dbotthepony.kstarbound.lua.userdata
|
||||
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.TableFactory
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState.Companion
|
||||
import ru.dbotthepony.kstarbound.util.CarriedExecutor
|
||||
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||
import ru.dbotthepony.kstarbound.world.entities.PathFinder
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class LuaPathFinder(val pacer: CarriedExecutor, val self: PathFinder) : Userdata<PathFinder>() {
|
||||
override fun getMetatable(): Table {
|
||||
return Companion.metatable
|
||||
}
|
||||
|
||||
override fun setMetatable(mt: Table?): Table {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getUserValue(): PathFinder {
|
||||
return self
|
||||
}
|
||||
|
||||
override fun setUserValue(value: PathFinder?): PathFinder {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private var isRunning = false
|
||||
|
||||
companion object {
|
||||
private val cost = "const".toByteString()!!
|
||||
private val action = "action".toByteString()!!
|
||||
private val jumpVelocity = "jumpVelocity".toByteString()!!
|
||||
private val source = "source".toByteString()!!
|
||||
private val target = "target".toByteString()!!
|
||||
|
||||
fun convertPath(tables: TableFactory, list: List<PathFinder.Edge>?): Table? {
|
||||
list ?: return null
|
||||
val table = tables.newTable(list.size, 0)
|
||||
var i = 1L
|
||||
|
||||
for (edge in list) {
|
||||
val edgeTable = tables.tableOf()
|
||||
edgeTable[cost] = edge.cost
|
||||
edgeTable[action] = edge.action.luaName
|
||||
edgeTable[jumpVelocity] = tables.from(edge.velocity)
|
||||
edgeTable[source] = edge.source.toTable(tables)
|
||||
edgeTable[target] = edge.target.toTable(tables)
|
||||
table[i++] = edgeTable
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
private fun __index(): Table {
|
||||
return metatable
|
||||
}
|
||||
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||
.add("explore", luaFunction { self: LuaPathFinder, maxExplore: Number? ->
|
||||
if (self.isRunning) {
|
||||
if (self.self.result.isEmpty)
|
||||
returnBuffer.setTo(null)
|
||||
else
|
||||
returnBuffer.setTo(self.self.result.value != null)
|
||||
} else if (self.self.result.isPresent) {
|
||||
returnBuffer.setTo(self.self.result.value != null)
|
||||
} else {
|
||||
val immediateMaxExplore = maxExplore?.toInt()?.times(4)?.coerceAtMost(800) ?: 800
|
||||
val result = self.self.run(immediateMaxExplore)
|
||||
|
||||
if (result != null) {
|
||||
returnBuffer.setTo(result)
|
||||
} else if (self.self.result.isEmpty) {
|
||||
// didn't explore enough, run in separate thread to not block main thread
|
||||
self.pacer.supplyAsync(self.self)
|
||||
self.isRunning = true
|
||||
returnBuffer.setTo(null)
|
||||
}
|
||||
}
|
||||
})
|
||||
.add("result", luaFunction { self: LuaPathFinder ->
|
||||
if (self.self.result.isEmpty)
|
||||
return@luaFunction returnBuffer.setTo()
|
||||
|
||||
val list = self.self.result.value ?: return@luaFunction returnBuffer.setTo()
|
||||
returnBuffer.setTo(convertPath(this, list))
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
@ -3,7 +3,9 @@ package ru.dbotthepony.kstarbound.lua.userdata
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder.Companion
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
|
||||
class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNoise>() {
|
||||
@ -28,7 +30,12 @@ class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNo
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun __index(): Table {
|
||||
return metatable
|
||||
}
|
||||
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||
.add("get", luaFunction { self: LuaPerlinNoise, x: Number, y: Number?, z: Number? ->
|
||||
if (y != null && z != null) {
|
||||
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble(), z.toDouble()])
|
||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.userdata
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
@ -63,7 +64,12 @@ class LuaRandomGenerator(var random: RandomGenerator) : Userdata<RandomGenerator
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun __index(): Table {
|
||||
return metatable
|
||||
}
|
||||
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
||||
.add("init", luaFunction { self: LuaRandomGenerator, seed: Long? ->
|
||||
self.random = random(seed ?: System.nanoTime())
|
||||
})
|
||||
|
@ -47,6 +47,11 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||
operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
|
||||
operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
|
||||
|
||||
operator fun plus(other: Vector2i) = AABB(mins + other, maxs + other)
|
||||
operator fun minus(other: Vector2i) = AABB(mins - other, maxs - other)
|
||||
operator fun times(other: Vector2i) = AABB(mins * other, maxs * other)
|
||||
operator fun div(other: Vector2i) = AABB(mins / other, maxs / other)
|
||||
|
||||
operator fun times(other: Double) = AABB(mins * other, maxs * other)
|
||||
operator fun div(other: Double) = AABB(mins / other, maxs / other)
|
||||
|
||||
@ -225,9 +230,15 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||
)
|
||||
}
|
||||
|
||||
fun padded(x: Double, y: Double): AABB {
|
||||
return AABB(
|
||||
mins - Vector2d(x, y),
|
||||
maxs + Vector2d(x, y)
|
||||
)
|
||||
}
|
||||
|
||||
fun expand(value: IStruct2d) = expand(value.component1(), value.component2())
|
||||
fun padded(value: IStruct2d) = expand(value.component1(), value.component2())
|
||||
fun padded(x: Double, y: Double) = expand(x, y)
|
||||
fun padded(value: IStruct2d) = padded(value.component1(), value.component2())
|
||||
|
||||
/**
|
||||
* Returns AABB which edges are expanded by [x] and [y] along their normals
|
||||
@ -308,6 +319,13 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||
)
|
||||
}
|
||||
|
||||
fun of(aabb: AABBi): AABB {
|
||||
return AABB(
|
||||
mins = Vector2d(aabb.mins.x.toDouble(), aabb.mins.y.toDouble()),
|
||||
maxs = Vector2d(aabb.maxs.x.toDouble(), aabb.maxs.y.toDouble()),
|
||||
)
|
||||
}
|
||||
|
||||
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
||||
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kommons.math.intersectRectangles
|
||||
import ru.dbotthepony.kommons.math.rectangleContainsRectangle
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
|
||||
init {
|
||||
@ -99,8 +100,14 @@ data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
|
||||
)
|
||||
}
|
||||
|
||||
fun padded(x: Int, y: Int) = expand(x, y)
|
||||
fun padded(value: IStruct2i) = expand(value.component1(), value.component2())
|
||||
fun padded(x: Int, y: Int): AABBi {
|
||||
return AABBi(
|
||||
mins - Vector2i(x, y),
|
||||
maxs + Vector2i(x, y)
|
||||
)
|
||||
}
|
||||
|
||||
fun padded(value: IStruct2i) = padded(value.component1(), value.component2())
|
||||
fun expand(value: IStruct2i) = expand(value.component1(), value.component2())
|
||||
|
||||
/**
|
||||
@ -151,5 +158,19 @@ data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
|
||||
}
|
||||
|
||||
@JvmField val ZERO = AABBi(Vector2i.ZERO, Vector2i.ZERO)
|
||||
|
||||
fun of(aabb: AABB): AABBi {
|
||||
return AABBi(
|
||||
Vector2i(aabb.mins.x.toInt(), aabb.mins.y.toInt()),
|
||||
Vector2i(aabb.maxs.x.toInt(), aabb.maxs.y.toInt()),
|
||||
)
|
||||
}
|
||||
|
||||
fun ofRound(aabb: AABB): AABBi {
|
||||
return AABBi(
|
||||
Vector2i(aabb.mins.x.roundToInt(), aabb.mins.y.roundToInt()),
|
||||
Vector2i(aabb.maxs.x.roundToInt(), aabb.maxs.y.roundToInt()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,9 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
||||
val difference: Vector2d
|
||||
get() = p1 - p0
|
||||
|
||||
val direction: Vector2d
|
||||
get() = difference.unitVector
|
||||
|
||||
fun reverse(): Line2d {
|
||||
return Line2d(p1, p0)
|
||||
}
|
||||
@ -157,6 +160,13 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
||||
return ((x - p0.x) * diff.x + (y - p0.y) * diff.y) / diff.lengthSquared
|
||||
}
|
||||
|
||||
fun rotate(angle: Double): Line2d {
|
||||
if (angle == 0.0)
|
||||
return this
|
||||
|
||||
return Line2d(p0.rotate(angle), p1.rotate(angle))
|
||||
}
|
||||
|
||||
fun distanceTo(other: IStruct2d, infinite: Boolean = false): Double {
|
||||
var proj = project(other)
|
||||
|
||||
|
@ -186,6 +186,9 @@ data class Vector2d(
|
||||
fun coerceAtMost(value: Vector2d): Vector2d { val (x, y) = value; return coerceAtMost(x, y) }
|
||||
|
||||
fun rotate(angle: Double): Vector2d {
|
||||
if (angle == 0.0)
|
||||
return this
|
||||
|
||||
val s = sin(angle)
|
||||
val c = cos(angle)
|
||||
|
||||
@ -193,6 +196,9 @@ data class Vector2d(
|
||||
}
|
||||
|
||||
fun toAngle(zeroAngle: Vector2d): Double {
|
||||
if (x == 0.0 && y == 0.0)
|
||||
return 0.0 // assume angle is zero
|
||||
|
||||
val dot = unitVector.dot(zeroAngle.unitVector)
|
||||
|
||||
return if (y > 0.0) {
|
||||
|
@ -17,7 +17,6 @@ import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||
@ -399,7 +398,6 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
NATIVE.add(::JoinWorldPacket)
|
||||
NATIVE.add(::ChunkCellsPacket)
|
||||
NATIVE.add(::ForgetChunkPacket)
|
||||
NATIVE.add(::SpawnWorldObjectPacket)
|
||||
NATIVE.add(::ForgetEntityPacket)
|
||||
NATIVE.add(::UniverseTimeUpdatePacket)
|
||||
NATIVE.add(::StepUpdatePacket)
|
||||
|
@ -4,9 +4,7 @@ import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kommons.io.ByteKey
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readByteKey
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.readMap
|
||||
@ -17,6 +15,8 @@ import ru.dbotthepony.kommons.io.writeMap
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.io.ByteKey
|
||||
import ru.dbotthepony.kstarbound.io.readByteKey
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
|
@ -1,21 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.ByteKey
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readByteKey
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.readMap
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeByteKey
|
||||
import ru.dbotthepony.kommons.io.writeKOptional
|
||||
import ru.dbotthepony.kommons.io.writeMap
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
|
||||
import ru.dbotthepony.kstarbound.io.ByteKey
|
||||
import ru.dbotthepony.kstarbound.io.readByteKey
|
||||
import ru.dbotthepony.kstarbound.io.writeByteKey
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
|
||||
|
@ -42,11 +42,16 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
||||
val old = value
|
||||
value = t
|
||||
queue.clear()
|
||||
bumpVersion()
|
||||
super.bumpVersion()
|
||||
valueListeners.forEach { it.callable.invoke(t, old) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun bumpVersion() {
|
||||
super.bumpVersion()
|
||||
valueListeners.forEach { it.callable.invoke(value, value) }
|
||||
}
|
||||
|
||||
override fun addListener(listener: Consumer<TYPE>): Listenable.L {
|
||||
return Listener(listener)
|
||||
}
|
||||
@ -68,7 +73,7 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
||||
val old = value
|
||||
value = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
|
||||
queue.clear()
|
||||
bumpVersion()
|
||||
super.bumpVersion()
|
||||
|
||||
if (value != old) {
|
||||
valueListeners.forEach { it.callable.invoke(value, old) }
|
||||
@ -85,7 +90,7 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
val read = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
|
||||
bumpVersion()
|
||||
super.bumpVersion()
|
||||
|
||||
if (isInterpolating) {
|
||||
queue.push(read, interpolationDelay)
|
||||
|
@ -77,6 +77,13 @@ class NetworkedMap<K, V>(
|
||||
})
|
||||
}
|
||||
|
||||
// forces re-network of specified index
|
||||
fun markIndexDirty(index: K) {
|
||||
if (index in this) {
|
||||
backlog.add(currentVersion() to Entry(Action.ADD, KOptional(nativeKey.copy(index)), KOptional(nativeValue.copy(this[index] as V)))) // not null!! assert because V might be nullable
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners = CopyOnWriteArrayList<Listener>()
|
||||
|
||||
private inner class Listener(val listener: ListenableMap.MapListener<K, V>) : Listenable.L {
|
||||
|
@ -19,6 +19,15 @@ class ChatHandler(val server: StarboundServer) {
|
||||
))
|
||||
}
|
||||
|
||||
fun systemMessage(target: ServerConnection, string: String) {
|
||||
target.send(ChatReceivePacket(
|
||||
ChatMessage(
|
||||
MessageContext.BROADCAST,
|
||||
text = string
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fun handle(source: ServerConnection, packet: ChatSendPacket) {
|
||||
when (packet.mode) {
|
||||
ChatSendMode.BROADCAST -> {
|
||||
|
@ -12,11 +12,7 @@ class IntegratedStarboundServer(val client: StarboundClient, root: File) : Starb
|
||||
|
||||
override fun tick0(delta: Double) {
|
||||
if (client.isShutdown) {
|
||||
shutdown()
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close0() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import java.io.Closeable
|
||||
import java.net.SocketAddress
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
@ -112,15 +113,18 @@ class ServerChannels(val server: StarboundServer) : Closeable {
|
||||
|
||||
val channel = ServerBootstrap().channel(LocalServerChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
|
||||
override fun initChannel(ch: Channel) {
|
||||
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
|
||||
lock.withLock {
|
||||
if (isClosed) return
|
||||
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
|
||||
|
||||
try {
|
||||
val connection = ServerConnection(server, ConnectionType.MEMORY)
|
||||
connections.add(connection)
|
||||
connection.bind(ch)
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
|
||||
ch.close()
|
||||
try {
|
||||
val connection = ServerConnection(server, ConnectionType.MEMORY)
|
||||
connections.add(connection)
|
||||
connection.bind(ch)
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
|
||||
ch.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}).bind(LocalAddress.ANY).syncUninterruptibly()
|
||||
@ -141,15 +145,18 @@ class ServerChannels(val server: StarboundServer) : Closeable {
|
||||
lock.withLock {
|
||||
val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
|
||||
override fun initChannel(ch: Channel) {
|
||||
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
|
||||
lock.withLock {
|
||||
if (isClosed) return
|
||||
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
|
||||
|
||||
try {
|
||||
val connection = ServerConnection(server, ConnectionType.NETWORK)
|
||||
connections.add(connection)
|
||||
connection.bind(ch)
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
|
||||
ch.close()
|
||||
try {
|
||||
val connection = ServerConnection(server, ConnectionType.NETWORK)
|
||||
connections.add(connection)
|
||||
connection.bind(ch)
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
|
||||
ch.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}).bind(localAddress).syncUninterruptibly()
|
||||
@ -168,9 +175,18 @@ class ServerChannels(val server: StarboundServer) : Closeable {
|
||||
override fun close() {
|
||||
lock.withLock {
|
||||
if (isClosed) return
|
||||
isClosed = true
|
||||
|
||||
connections.forEach { it.disconnect("Server shutting down") }
|
||||
channels.forEach { it.channel().close() }
|
||||
connections.forEach { it.disconnect("Server shutting down") }
|
||||
|
||||
// aeugh
|
||||
connections.forEach {
|
||||
if (!it.waitDisconnect(2L, TimeUnit.SECONDS)) {
|
||||
LOGGER.warn("Giving up waiting for $it to disconnect")
|
||||
}
|
||||
}
|
||||
|
||||
channels.clear()
|
||||
connections.clear()
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user