Compare commits

...

32 Commits

Author SHA1 Message Date
a17bb2a732
Don't know why, but all monsters do is constantly walk to left 2024-07-13 18:05:17 +07:00
6f2b8b7bbb
i regret going for third-party Lua-in-JVM implementation due to how bugged it is 2024-07-07 19:32:52 +07:00
307c67d976
Relaxed path finder "reached" check 2024-06-28 23:05:38 +07:00
d9b25c960d
Fix returnBest not being obeyed 2024-06-28 23:00:56 +07:00
34dcc68e15
Optimize StatusController.updateStats 2024-06-28 22:55:10 +07:00
f95bc9762f
Minimally working Monster entities
PathController, PathFinder

Actor movement controller Lua bindings

Game loading no longer block Universe thread, more efficient registry population synchronization

Environmental status effects now can be stat modifiers
2024-06-28 22:44:13 +07:00
5c6efaf03d
Implement quirk of Lua reference impl 'next', where removed key do not immediately become invisible to 'next' 2024-06-27 21:17:13 +07:00
77a9beb665
Per player system message, display notification in chat when player is kicked off world 2024-06-02 21:52:17 +07:00
56c154cc96
provideConfigBindings 2024-05-25 21:16:46 +07:00
242f372819
Don't do anything in invokeGlobal if init has not been called 2024-05-25 19:33:11 +07:00
1d77cf8f98
String.limit 2024-05-24 12:27:19 +07:00
56f4fe46a6
Kick player who spams entity messages instead of rejecting them 2024-05-22 19:05:12 +07:00
e2d27e34a5
Decouple add from nextFreeIndex in IdMap 2024-05-22 19:04:37 +07:00
5769cff60b
Wait for clients to disconnect (within reasonable timeframe) before initiating server shutdown 2024-05-22 19:04:05 +07:00
8ef6bead2c
Add missing defaults to StatusControllerConfig 2024-05-22 19:03:42 +07:00
6729f2decc
BasicNetworkedElement.bumpVersion call from outside now properly notifies listeners 2024-05-22 19:03:11 +07:00
06a202bf17
applyParameters variant for ActorMovementController 2024-05-22 19:02:47 +07:00
8eb1db919f
Intern strings originating from Lua 2024-05-22 19:02:19 +07:00
e81079479b
Add PersistentStatusEffect as typealias 2024-05-22 19:02:04 +07:00
d1865900f6
JsonPatch apply now accepts json reader promise 2024-05-22 19:01:49 +07:00
69e0a8737e
Update Color replacement to be actually useful 2024-05-22 19:01:27 +07:00
b41d45b3e9
Synchronize item registry loading using universe thread 2024-05-22 19:01:05 +07:00
a2cc4ba6c3
Cleaner server shutdown 2024-05-22 19:00:42 +07:00
0b3aac6189
Game loading now no longer blocks universe thread and it can perform other work 2024-05-22 18:59:57 +07:00
2f782d7825
NetworkedMap.markIndexDirty 2024-05-22 18:59:13 +07:00
b4902559ac
Fix crash related to starbound data structure being dumb 2024-05-22 18:59:02 +07:00
1b7076f04f
Use Lua's random in util bindings 2024-05-22 18:58:30 +07:00
96fdcccdd0
More random generator compat with original engine 2024-05-22 18:58:11 +07:00
032f32626e
Make RGBAColorTypeAdapter accept hex strings as input 2024-05-22 18:57:42 +07:00
16ccb84d3b
Don't let through long nicknames 2024-05-16 22:25:26 +07:00
19324c6247
Add cli command arguments 2024-05-16 19:32:08 +07:00
67ac2b272e
Busy.png makes a nice loading icon 2024-05-12 21:10:37 +07:00
151 changed files with 7491 additions and 943 deletions

View File

@ -4,7 +4,7 @@
This document briefly documents what have been added (or removed) regarding modding capabilities or engine behavior(s) 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, 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 * `treasurechests` now can specify `treasurePool` as array
* `damageTable` can be defined directly, without referencing other JSON file (experimental feature) * `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 ## Biomes
* Tree biome placeables now have `variantsRange` (defaults to `[1, 1]`) and `subVariantsRange` (defaults to `[2, 2]`) * 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), * `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) * 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 * `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 * 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 * In tiled, you already can do this using `"quantity"` property
@ -43,7 +44,7 @@ val color: TileColor = TileColor.DEFAULT
## .terrain ## .terrain
Please keep in mind that if you use new format or new terrain selectors original clients will 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 * 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"}` * 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 ## .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. * `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 # 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`) * 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 * 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` * 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.hasEffect(effect: string): boolean`
* Added `animator.parts(): List<string>` * 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 ## world
#### Additions #### Additions
@ -244,7 +286,10 @@ _slightly_ different results from execution to execution,
such as one microdungeon taking precedence over another microdungeon 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, 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 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).
--------------- ---------------

View File

@ -23,6 +23,7 @@ val log4jVersion: String by project
val guavaVersion: String by project val guavaVersion: String by project
val junitVersion: String by project val junitVersion: String by project
val sqliteVersion: String by project val sqliteVersion: String by project
val picocliVersion: String by project
repositories { repositories {
maven { maven {
@ -91,6 +92,8 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion") implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion")
implementation(project(":luna")) implementation(project(":luna"))
implementation("info.picocli:picocli:$picocliVersion")
implementation("org.xerial:sqlite-jdbc:$sqliteVersion") implementation("org.xerial:sqlite-jdbc:$sqliteVersion")
implementation("io.netty:netty-transport:$nettyVersion") implementation("io.netty:netty-transport:$nettyVersion")

View File

@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
kotlinVersion=1.9.10 kotlinVersion=1.9.10
kotlinCoroutinesVersion=1.8.0 kotlinCoroutinesVersion=1.8.0
kommonsVersion=2.17.0 kommonsVersion=3.0.1
ffiVersion=2.2.13 ffiVersion=2.2.13
lwjglVersion=3.3.0 lwjglVersion=3.3.0
@ -17,3 +17,5 @@ log4jVersion=2.17.1
guavaVersion=33.0.0-jre guavaVersion=33.0.0-jre
junitVersion=5.8.2 junitVersion=5.8.2
sqliteVersion=3.45.3.0 sqliteVersion=3.45.3.0
picocliVersion=4.7.6

View File

@ -1344,7 +1344,7 @@ public final class StringLib {
} }
} while (idx >= 0); } while (idx >= 0);
context.getReturnBuffer().setTo(bld.toString()); context.getReturnBuffer().setTo(ByteString.of(bld.toString()));
} }
private static class SuspendedState { private static class SuspendedState {

View File

@ -46,6 +46,9 @@ public class TraversableHashMap<K, V> implements Map<K, V> {
private final Set<Map.Entry<K, V>> entrySet; private final Set<Map.Entry<K, V>> entrySet;
private K firstKey; private K firstKey;
private K lastKey; 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. * 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); Entry<K, V> e = entries.remove(key);
if (e != null) { if (e != null) {
K prevKey = e.getPreviousKey(); K prevKey = e.getPreviousKey();
K nextKey = e.getNextKey(); K nextKey = e.getNextKey();
lingeringKey = (K) key;
lingeringSuccessor = nextKey;
if (prevKey != null) { if (prevKey != null) {
entries.get(prevKey).setNextKey(nextKey); entries.get(prevKey).setNextKey(nextKey);
} else { } else {
@ -202,6 +207,9 @@ public class TraversableHashMap<K, V> implements Map<K, V> {
Objects.requireNonNull(key); Objects.requireNonNull(key);
Entry<K, V> e = entries.get(key); Entry<K, V> e = entries.get(key);
if (e == null) { if (e == null) {
if (key.equals(lingeringKey))
return lingeringSuccessor;
throw new NoSuchElementException(key.toString()); throw new NoSuchElementException(key.toString());
} }
return e.getNextKey(); return e.getNextKey();

View File

@ -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)) 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: 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))

View File

@ -13,13 +13,16 @@ import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
import ru.dbotthepony.kstarbound.defs.ClientConfig import ru.dbotthepony.kstarbound.defs.ClientConfig
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
import ru.dbotthepony.kstarbound.defs.ElementalDamageType
import ru.dbotthepony.kstarbound.defs.MovementParameters import ru.dbotthepony.kstarbound.defs.MovementParameters
import ru.dbotthepony.kstarbound.defs.world.SpawnerConfig
import ru.dbotthepony.kstarbound.defs.UniverseServerConfig 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.PlayerConfig
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig
import ru.dbotthepony.kstarbound.defs.item.ItemGlobalConfig 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.tile.TileDamageParameters
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig 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.SystemWorldObjectConfig
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.mapAdapter
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
@ -118,6 +120,15 @@ object Globals {
var shipUpgrades by Delegates.notNull<ShipUpgrades>() var shipUpgrades by Delegates.notNull<ShipUpgrades>()
private set 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>>() private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()
val profanityFilter: ImmutableSet<String> by lazy { val profanityFilter: ImmutableSet<String> by lazy {
@ -224,11 +235,14 @@ object Globals {
tasks.add(load("/plants/bushDamage.config", ::bushDamage)) tasks.add(load("/plants/bushDamage.config", ::bushDamage))
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage)) tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
tasks.add(load("/ships/shipupgrades.config", ::shipUpgrades)) 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("/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("/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("/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("/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()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())

View File

@ -1,31 +1,79 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.lib.CoroutineLib
import org.classdump.luna.runtime.Coroutine
import org.lwjgl.Version 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.client.StarboundClient
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
import java.io.File import java.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.concurrent.Callable
import kotlin.system.exitProcess
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun main() { @Command(
Starbound.addArchive(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) 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()!!) { @Option(names = ["-s", "--storage"], description = ["Location where to store game files.", "Defaults to game's 'storage' subfolder"])
for (f2 in f.listFiles()!!) { var storageFolder: String? = null
if (f2.isFile) {
Starbound.addArchive(f2) 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 { Starbound.addArchive(file)
val server = IntegratedStarboundServer(client, File("./storage")) //Starbound.addPath(File("K:\\git\\kstarbound\\unpacked_assets"))
server.channels.createChannel(InetSocketAddress(21060))
}.exceptionally { LOGGER.error("what", it); null } /*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)
}
} }

View File

@ -3,10 +3,12 @@ package ru.dbotthepony.kstarbound
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapterFactory import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
@ -14,6 +16,7 @@ import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.DamageKind
import ru.dbotthepony.kstarbound.defs.Json2Function import ru.dbotthepony.kstarbound.defs.Json2Function
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.JsonFunction 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.dungeon.DungeonDefinition
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
import ru.dbotthepony.kstarbound.defs.ProjectileDefinition 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.quest.QuestTemplate
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters 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.GrassVariant
import ru.dbotthepony.kstarbound.defs.world.TreeVariant import ru.dbotthepony.kstarbound.defs.world.TreeVariant
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
import ru.dbotthepony.kstarbound.defs.world.SpawnType
import ru.dbotthepony.kstarbound.item.ItemRegistry import ru.dbotthepony.kstarbound.item.ItemRegistry
import ru.dbotthepony.kstarbound.json.JsonPatch import ru.dbotthepony.kstarbound.json.JsonPatch
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.fromJsonTreeFast import ru.dbotthepony.kstarbound.json.fromJsonTreeFast
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.world.physics.CollisionType
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.random.RandomGenerator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap
object Registries { object Registries {
private val LOGGER = LogManager.getLogger() 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 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 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 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 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 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()) } 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 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 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 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?>> { private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, KOptional<Int?>> {
return { mapper.invoke(it) to KOptional() } return { mapper.invoke(it) to KOptional() }
@ -122,7 +186,7 @@ object Registries {
return files.map { listedFile -> return files.map { listedFile ->
Starbound.GLOBAL_SCOPE.launch { Starbound.GLOBAL_SCOPE.launch {
try { 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()) { AssetPathStack(listedFile.computeFullPath()) {
val read = adapter.fromJsonTreeFast(elem) val read = adapter.fromJsonTreeFast(elem)
@ -130,7 +194,7 @@ object Registries {
after(read, listedFile) after(read, listedFile)
registry.add { Starbound.submit {
registry.add( registry.add(
key = keys.first, key = keys.first,
value = read, value = read,
@ -138,7 +202,7 @@ object Registries {
json = elem, json = elem,
file = listedFile file = listedFile
) )
} }.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition file $listedFile", err); null }
} }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Loading ${registry.name} definition file $listedFile", err) 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<*>> { fun load(fileTree: Map<String, Collection<IStarboundFile>>, patchTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
val tasks = ArrayList<Future<*>>() val tasks = ArrayList<Future<*>>()
@ -165,14 +225,18 @@ object Registries {
tasks.add(loadMetaMaterials()) tasks.add(loadMetaMaterials())
tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name))) 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(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(statusEffects, patchTree, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
tasks.addAll(loadRegistry(species, patchTree, fileTree["species"] ?: listOf(), key(Species::kind))) 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(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(questTemplates, patchTree, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
tasks.addAll(loadRegistry(techs, patchTree, fileTree["tech"] ?: listOf(), key(TechDefinition::name))) 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(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(biomes, patchTree, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name)))
tasks.addAll(loadRegistry(grassVariants, patchTree, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::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))) 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(bushVariants, patchTree, fileTree["bush"] ?: listOf(), key(BushVariant.Data::name)))
tasks.addAll(loadRegistry(markovGenerators, patchTree, fileTree["namesource"] ?: listOf(), key(MarkovTextGenerator::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(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(jsonFunctions, fileTree["functions"] ?: listOf(), patchTree))
tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: 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(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: 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 return tasks
} }
@ -196,16 +268,16 @@ object Registries {
return files.map { listedFile -> return files.map { listedFile ->
Starbound.GLOBAL_SCOPE.launch { Starbound.GLOBAL_SCOPE.launch {
try { 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()) { for ((k, v) in json.entrySet()) {
try { try {
val value = adapter.fromJsonTreeFast(v) val value = adapter.fromJsonTreeFast(v)
transform(value, k) transform(value, k)
registry.add { Starbound.submit {
registry.add(k, value, v, listedFile) registry.add(k, value, v, listedFile)
} }.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err); null }
} catch (err: Exception) { } catch (err: Exception) {
LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err) 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>>) { private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
try { 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 name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
val factory = TerrainSelectorType.factory(json, false, type) val factory = TerrainSelectorType.factory(json, false, type)
terrainSelectors.add { Starbound.submit {
terrainSelectors.add(name, factory) terrainSelectors.add(name, factory)
} }
} catch (err: Exception) { } catch (err: Exception) {
@ -268,7 +367,7 @@ object Registries {
val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTreeFast(read) val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTreeFast(read)
for (def in read2) { for (def in read2) {
tiles.add { Starbound.submit {
tiles.add(key = "metamaterial:${def.name}", id = KOptional(def.materialId), value = TileDefinition( tiles.add(key = "metamaterial:${def.name}", id = KOptional(def.materialId), value = TileDefinition(
isMeta = true, isMeta = true,
materialId = def.materialId, materialId = def.materialId,

View File

@ -7,48 +7,34 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectMap
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap 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.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.util.KOptional 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.Collections
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.collections.set import kotlin.collections.set
import kotlin.concurrent.read import kotlin.concurrent.read
import kotlin.concurrent.withLock
import kotlin.concurrent.write 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 keysInternal = HashMap<String, Impl>()
private val idsInternal = Int2ObjectOpenHashMap<Impl>() private val idsInternal = Int2ObjectOpenHashMap<Impl>()
private val keyRefs = HashMap<String, RefImpl>() private val keyRefs = HashMap<String, RefImpl>()
private val idRefs = Int2ObjectOpenHashMap<RefImpl>() private val idRefs = Int2ObjectOpenHashMap<RefImpl>()
private val backlog = ConcurrentLinkedQueue<Runnable>()
private val lock = ReentrantReadWriteLock() private val lock = ReentrantReadWriteLock()
private var hasBeenValidated = false private var hasBeenValidated = false
private val loggedMisses = ObjectOpenHashSet<String>()
// it is much cheaper to queue registry additions rather than locking during high congestion // idiot-proof miss lookup. Surely, it will cause some entries to be never logged
fun add(task: Runnable) { // if they are missing, but at least if malicious actor spams with long-ass invalid data
backlog.add(task) // it won't explode memory usage of server
} private val loggedMisses = LongOpenHashSet()
fun finishLoad() {
lock.write {
var next = backlog.poll()
while (next != null) {
next.run()
next = backlog.poll()
}
}
}
val keys: Map<String, Entry<T>> = Collections.unmodifiableMap(keysInternal) val keys: Map<String, Entry<T>> = Collections.unmodifiableMap(keysInternal)
val ids: Int2ObjectMap<out Entry<T>> = Int2ObjectMaps.unmodifiable(idsInternal) val ids: Int2ObjectMap<out Entry<T>> = Int2ObjectMaps.unmodifiable(idsInternal)
@ -102,8 +88,21 @@ class Registry<T : Any>(val name: String) {
return this === other 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 { override fun hashCode(): Int {
return key.hashCode() return hash
} }
override fun toString(): String { 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 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 { override fun hashCode(): Int {
return key.hashCode() return hash
} }
override fun toString(): String { override fun toString(): String {
@ -138,9 +150,13 @@ class Registry<T : Any>(val name: String) {
val result = lock.read { keysInternal[index] } val result = lock.read { keysInternal[index] }
if (result == null && hasBeenValidated) { if (result == null && hasBeenValidated) {
val hasher = XXHash64()
hasher.update(index.toByteArray())
val missIndex = hasher.digestAsLong()
lock.write { lock.write {
if (loggedMisses.add(index)) { if (loggedMisses.add(missIndex)) {
LOGGER.warn("No such $name: $index") LOGGER.warn("No such $name: ${index.limit()}")
} }
} }
} }
@ -152,8 +168,15 @@ class Registry<T : Any>(val name: String) {
val result = lock.read { idsInternal[index] } val result = lock.read { idsInternal[index] }
if (result == null && hasBeenValidated) { 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 { lock.write {
if (loggedMisses.add(index.toString())) { if (loggedMisses.add(missIndex)) {
LOGGER.warn("No such $name: ID $index") 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") 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 { fun ref(index: String): Ref<T> = lock.write {
keyRefs.computeIfAbsent(index, Object2ObjectFunction { keyRefs.computeIfAbsent(index, Object2ObjectFunction {
val ref = RefImpl(Either.left(it as String)) val ref = RefImpl(Either.left(it as String))
ref.entry = keysInternal[it] ref.entry = keysInternal[it]
if (hasBeenValidated && ref.entry == null && loggedMisses.add(it)) { if (hasBeenValidated && ref.entry == null) {
LOGGER.warn("No such $name: $it") val hasher = XXHash64()
hasher.update(index.toByteArray())
if (loggedMisses.add(hasher.digestAsLong())) {
LOGGER.warn("No such $name: ${it.limit()}")
}
} }
ref 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 { fun ref(index: Int): Ref<T> = lock.write {
idRefs.computeIfAbsent(index, Int2ObjectFunction { idRefs.computeIfAbsent(index, Int2ObjectFunction {
val ref = RefImpl(Either.right(it)) val ref = RefImpl(Either.right(it))
ref.entry = idsInternal[it] ref.entry = idsInternal[it]
if (hasBeenValidated && ref.entry == null && loggedMisses.add(it.toString())) { if (hasBeenValidated && ref.entry == null) {
LOGGER.warn("No such $name: ID $it") 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 ref
@ -261,7 +307,10 @@ class Registry<T : Any>(val name: String) {
} }
entry.value = value entry.value = value
entry.json = json
if (storeJson)
entry.json = json
entry.file = file entry.file = file
entry.isBuiltin = isBuiltin entry.isBuiltin = isBuiltin

View File

@ -13,8 +13,10 @@ import kotlinx.coroutines.Runnable
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.compiler.CompilerChunkLoader import org.classdump.luna.compiler.CompilerChunkLoader
import org.classdump.luna.compiler.CompilerSettings import org.classdump.luna.compiler.CompilerSettings
@ -344,7 +346,6 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
registerTypeAdapterFactory(JsonReference.Companion) registerTypeAdapterFactory(JsonReference.Companion)
registerTypeAdapter(ColorReplacements.Companion)
registerTypeAdapterFactory(BlueprintLearnList.Companion) registerTypeAdapterFactory(BlueprintLearnList.Companion)
registerTypeAdapter(RGBAColorTypeAdapter) registerTypeAdapter(RGBAColorTypeAdapter)
@ -619,7 +620,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
return files return files
} }
private fun doInitialize() { private suspend fun doInitialize() {
if (!initializing && !initialized) { if (!initializing && !initialized) {
initializing = true initializing = true
} else { } else {
@ -665,21 +666,29 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
loaded = toLoad - tasks.size loaded = toLoad - tasks.size
loadingProgress = (total - tasks.size) / total loadingProgress = (total - tasks.size) / total
loadingProgressText = "Loading JSON assets, $loaded / $toLoad" loadingProgressText = "Loading JSON assets, $loaded / $toLoad"
LockSupport.parkNanos(5_000_000L) delay(10L)
} }
Registries.finishLoad()
RecipeRegistry.finishLoad()
ItemRegistry.finishLoad() ItemRegistry.finishLoad()
Registries.validate() Registries.validate()
initializing = false initializing = false
initialized = true initialized = true
// Suggest VM to reclaim memory after game has been loaded
System.gc()
} }
private var initializationFuture: CompletableFuture<*>? = null
fun initializeGame(): CompletableFuture<*> { 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 private var fontPath: File? = null

View File

@ -109,9 +109,9 @@ interface IStarboundFile : ISBFileLocator {
val children = file.children ?: return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path) val children = file.children ?: return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
val find = children[split[splitIndex]] val find = children[split[splitIndex]]
if (find is StarboundPak.SBDirectory) { if (find is StarboundPak.SBDirectory || find != null && find.isDirectory) {
file = find file = find
} else if (find is StarboundPak.SBFile) { } else if (find is StarboundPak.SBFile || find != null && find.isFile) {
if (splitIndex + 1 != split.size) { if (splitIndex + 1 != split.size) {
return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path) 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 get() = real.isFile
override val children: Map<String, PhysicalFile>? override val children: Map<String, PhysicalFile>?
get() { 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 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 fullPatch by lazy { super.computeFullPath().sbIntern() }
private val directory by lazy { super.computeDirectory(false).sbIntern() } private val directory by lazy { super.computeDirectory(false).sbIntern() }

View File

@ -62,6 +62,7 @@ import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.BlockableEventLoop import ru.dbotthepony.kstarbound.util.BlockableEventLoop
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.util.supplyAsync
import ru.dbotthepony.kstarbound.world.ChunkState import ru.dbotthepony.kstarbound.world.ChunkState
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.RayDirection import ru.dbotthepony.kstarbound.world.RayDirection
@ -78,12 +79,14 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.ForkJoinWorkerThread
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Function
import java.util.function.IntConsumer import java.util.function.IntConsumer
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
@ -335,6 +338,33 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
texture 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 missingTexture by lazy(LazyThreadSafetyMode.NONE) {
val texture = GLTexture2D(8, 8, GL_RGB8) val texture = GLTexture2D(8, 8, GL_RGB8)
val buffer = ByteBuffer.allocateDirect(3 * 8 * 8) val buffer = ByteBuffer.allocateDirect(3 * 8 * 8)
@ -621,26 +651,14 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
private var renderedLoadingScreen = false private var renderedLoadingScreen = false
private fun renderLoadingScreen() { private fun renderBusyDots() {
performOpenGLCleanup()
updateViewportParams()
clearColor = RGBAColor.BLACK
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
val min = viewportHeight.coerceAtMost(viewportWidth) val min = viewportHeight.coerceAtMost(viewportWidth)
val size = min * 0.02f val size = min * 0.02f
val program = programs.positionColor val program = programs.positionColor
val builder = program.builder val builder = program.builder
uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
stack.clear(Matrix3f.identity())
program.colorMultiplier = RGBAColor.WHITE program.colorMultiplier = RGBAColor.WHITE
builder.builder.begin(GeometryType.QUADS) builder.builder.begin(GeometryType.QUADS)
if (dotCountdown-- <= 0) { 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(a) }
builder.builder.centeredQuad(viewportWidth * 0.5f + size * 2f, viewportHeight * 0.5f, size, size) { color(c) } 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) } builder.builder.quad(0f, viewportHeight - 20f, viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight.toFloat()) { color(RGBAColor.GREEN) }
GLFW.glfwSetWindowTitle(window, "KStarbound: ${Starbound.loadingProgressText}") GLFW.glfwSetWindowTitle(window, "KStarbound: ${Starbound.loadingProgressText}")
@ -671,7 +752,6 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
val runtime = Runtime.getRuntime() 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, 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, 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) } 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(), leftEdge + viewportWidth * 0.7f * ((runtime.totalMemory() - runtime.freeMemory()) / runtime.maxMemory().toDouble()).toFloat(),
viewportHeight * 0.1f + 19f viewportHeight * 0.1f + 19f
) { color(RGBAColor(29, 140, 160)) } ) { color(RGBAColor(29, 140, 160)) }
//}
if (fontInitialized) { if (fontInitialized) {
drawPerformanceBasic(true) drawPerformanceBasic(true)

View File

@ -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)
}
}
}

View File

@ -19,7 +19,7 @@ class IdMap<T : Any>(val min: Int = 0, val max: Int = Int.MAX_VALUE, private val
return nextIndex return nextIndex
} }
fun add(value: T): Int { fun nextFreeIndex(): Int {
var i = 0 var i = 0
var index = next() 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 map[index] = value
return index return index
} }

View File

@ -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,
)
}
}

View File

@ -1,13 +1,17 @@
package ru.dbotthepony.kstarbound.defs package ru.dbotthepony.kstarbound.defs
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.math.RGBAColor
@JsonAdapter(ColorReplacements.Adapter::class)
class ColorReplacements private constructor(private val mapping: Int2IntOpenHashMap) { class ColorReplacements private constructor(private val mapping: Int2IntOpenHashMap) {
constructor(mapping: Map<Int, Int>) : this(Int2IntOpenHashMap(mapping)) 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) return mapping.getOrDefault(color, color)
} }
companion object : TypeAdapter<ColorReplacements>() { fun toImageOperator(): String {
val EMPTY = ColorReplacements(Int2IntOpenHashMap()) 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?) { override fun write(out: JsonWriter, value: ColorReplacements?) {
if (value == null) if (value == null)
out.nullValue() out.nullValue()
@ -25,8 +31,8 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
out.beginObject() out.beginObject()
for ((k, v) in value.mapping) { for ((k, v) in value.mapping) {
out.name(k.toString(16)) out.name(RGBAColor.rgb(k).toHexStringRGB())
out.value(v.toString(16)) out.value(RGBAColor.rgb(v).toHexStringRGB())
} }
out.endObject() out.endObject()
@ -40,7 +46,7 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
if (`in`.nextString() != "") if (`in`.nextString() != "")
throw JsonSyntaxException("Invalid color replacement definition near ${`in`.path}") throw JsonSyntaxException("Invalid color replacement definition near ${`in`.path}")
return ColorReplacements.EMPTY return EMPTY
} }
val mapping = Int2IntOpenHashMap() val mapping = Int2IntOpenHashMap()
@ -48,10 +54,20 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
`in`.beginObject() `in`.beginObject()
while (`in`.peek() != JsonToken.END_OBJECT) { while (`in`.peek() != JsonToken.END_OBJECT) {
val k = `in`.nextName() val kGet = `in`.nextName()
val v = `in`.nextString() 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() `in`.endObject()
@ -59,4 +75,8 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
return ColorReplacements(mapping) return ColorReplacements(mapping)
} }
} }
companion object {
val EMPTY = ColorReplacements(Int2IntOpenHashMap())
}
} }

View File

@ -1,8 +1,10 @@
package ru.dbotthepony.kstarbound.defs package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter import com.google.gson.annotations.JsonAdapter
@ -81,6 +83,13 @@ enum class DamageType(override val jsonName: String) : IStringSerializable {
STATUS("Environment"); STATUS("Environment");
} }
@JsonFactory
data class DamageKind(
val kind: String,
val elementalType: String = "default",
val effects: JsonObject = JsonObject()
)
@JsonFactory @JsonFactory
data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) { data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort()) constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort())
@ -189,10 +198,10 @@ data class DamageData(
val hitType: HitType, val hitType: HitType,
val damageType: DamageType, val damageType: DamageType,
val damage: Double, val damage: Double,
val knockback: Vector2d, val knockbackMomentum: Vector2d,
val sourceEntityId: Int, val sourceEntityId: Int,
val inflictorEntityId: Int = 0, val inflictorEntityId: Int = 0,
val kind: String, val damageSourceKind: String,
val statusEffects: Collection<EphemeralStatusEffect>, val statusEffects: Collection<EphemeralStatusEffect>,
) { ) {
constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this( constructor(stream: DataInputStream, isLegacy: Boolean, inflictorEntityId: Int) : this(
@ -213,9 +222,9 @@ data class DamageData(
stream.writeEnumStupid(hitType.ordinal, isLegacy) stream.writeEnumStupid(hitType.ordinal, isLegacy)
stream.writeByte(damageType.ordinal) stream.writeByte(damageType.ordinal)
stream.writeDouble(damage, isLegacy) stream.writeDouble(damage, isLegacy)
stream.writeStruct2d(knockback, isLegacy) stream.writeStruct2d(knockbackMomentum, isLegacy)
stream.writeInt(sourceEntityId) stream.writeInt(sourceEntityId)
stream.writeBinaryString(kind) stream.writeBinaryString(damageSourceKind)
stream.writeCollection(statusEffects) { it.write(this, isLegacy) } stream.writeCollection(statusEffects) { it.write(this, isLegacy) }
} }
} }
@ -363,3 +372,5 @@ data class DamageSource(
val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write) val LEGACY_CODEC = legacyCodec(::DamageSource, DamageSource::write)
} }
} }
data class ElementalDamageType(val resistanceStat: String, val damageNumberParticles: ImmutableMap<HitType, String>)

View File

@ -1,16 +1,22 @@
package ru.dbotthepony.kstarbound.defs package ru.dbotthepony.kstarbound.defs
import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import ru.dbotthepony.kommons.io.readBinaryString import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kstarbound.Registries 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.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity 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.ProjectileEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity 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.PlantEntity
import ru.dbotthepony.kstarbound.world.entities.tile.PlantPieceEntity import ru.dbotthepony.kstarbound.world.entities.tile.PlantPieceEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject 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 { 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) { MONSTER("monster", "MonsterEntity", false, false) {
override fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity { override fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity {
TODO("MONSTER") return MonsterEntity(MonsterVariant.read(stream, isLegacy))
} }
override fun fromStorage(data: JsonObject): AbstractEntity { 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
} }
}, },

View File

@ -6,39 +6,41 @@ import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.writeBinaryString 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.readNullableDouble
import ru.dbotthepony.kstarbound.io.writeNullable
import ru.dbotthepony.kstarbound.io.writeNullableDouble import ru.dbotthepony.kstarbound.io.writeNullableDouble
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@JsonAdapter(EphemeralStatusEffect.Adapter::class) @JsonAdapter(EphemeralStatusEffect.Adapter::class)
data class EphemeralStatusEffect(val effect: String, val duration: Double? = null) { data class EphemeralStatusEffect(val effect: Registry.Ref<StatusEffectDefinition>, val duration: Double? = null) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInternedString(), stream.readNullableDouble()) 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>() { class Adapter(gson: Gson) : TypeAdapter<EphemeralStatusEffect>() {
private val factory = FactoryAdapter.createFor(EphemeralStatusEffect::class, gson = gson) private val factory = FactoryAdapter.createFor(EphemeralStatusEffect::class, gson = gson)
override fun write(out: JsonWriter, value: EphemeralStatusEffect) { override fun write(out: JsonWriter, value: EphemeralStatusEffect) {
if (value.duration == null) if (value.duration == null)
out.value(value.effect) out.value(value.effect.key.left())
else else
factory.write(out, value) factory.write(out, value)
} }
override fun read(`in`: JsonReader): EphemeralStatusEffect { override fun read(`in`: JsonReader): EphemeralStatusEffect {
if (`in`.peek() == JsonToken.STRING) if (`in`.peek() == JsonToken.STRING)
return EphemeralStatusEffect(`in`.nextString()) return EphemeralStatusEffect(Registries.statusEffects.ref(`in`.nextString()))
else else
return factory.read(`in`) return factory.read(`in`)
} }
} }
fun write(stream: DataOutputStream, isLegacy: Boolean) { fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeBinaryString(effect) stream.writeBinaryString(effect.key.left())
stream.writeNullableDouble(duration, isLegacy) stream.writeNullableDouble(duration, isLegacy)
} }
} }

View File

@ -118,7 +118,7 @@ sealed class SpawnTarget {
tickets.addAll(world.permanentChunkTicket(testRect).await()) tickets.addAll(world.permanentChunkTicket(testRect).await())
tickets.forEach { it.chunk.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 return testPos
} }

View File

@ -21,7 +21,7 @@ import java.util.UUID
@JsonAdapter(WorldID.Adapter::class) @JsonAdapter(WorldID.Adapter::class)
sealed class WorldID { sealed class WorldID {
abstract fun write(stream: DataOutputStream, isLegacy: Boolean) abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
val isLimbo: Boolean get() = this is Limbo val isLimbo: Boolean get() = this === Limbo
object Limbo : WorldID() { object Limbo : WorldID() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) { override fun write(stream: DataOutputStream, isLegacy: Boolean) {

View File

@ -1,9 +1,9 @@
package ru.dbotthepony.kstarbound.defs.actor.player package ru.dbotthepony.kstarbound.defs.actor
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@JsonFactory @JsonFactory
class ActorMovementModifiers( data class ActorMovementModifiers(
val groundMovementModifier: Double = 1.0, val groundMovementModifier: Double = 1.0,
val liquidMovementModifier: Double = 1.0, val liquidMovementModifier: Double = 1.0,
val speedModifier: Double = 1.0, val speedModifier: Double = 1.0,
@ -11,10 +11,10 @@ class ActorMovementModifiers(
val liquidJumpModifier: Double = 1.0, val liquidJumpModifier: Double = 1.0,
val runningSuppressed: Boolean = false, val runningSuppressed: Boolean = false,
val jumpingSuppressed: Boolean = false, val jumpingSuppressed: Boolean = false,
val facingSuppressed: Boolean = false,
val movementSuppressed: Boolean = false, val movementSuppressed: Boolean = false,
val facingSuppressed: Boolean = false,
) { ) {
fun combine(other: ActorMovementModifiers): ActorMovementModifiers { fun merge(other: ActorMovementModifiers): ActorMovementModifiers {
return ActorMovementModifiers( return ActorMovementModifiers(
groundMovementModifier = groundMovementModifier * other.groundMovementModifier, groundMovementModifier = groundMovementModifier * other.groundMovementModifier,
liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier, liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier,
@ -23,8 +23,8 @@ class ActorMovementModifiers(
liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier, liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier,
runningSuppressed = runningSuppressed || other.runningSuppressed, runningSuppressed = runningSuppressed || other.runningSuppressed,
jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed, jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed,
facingSuppressed = facingSuppressed || other.facingSuppressed,
movementSuppressed = movementSuppressed || other.movementSuppressed, movementSuppressed = movementSuppressed || other.movementSuppressed,
facingSuppressed = facingSuppressed || other.facingSuppressed,
) )
} }

View File

@ -10,19 +10,19 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@JsonFactory @JsonFactory
data class StatusControllerConfig( data class StatusControllerConfig(
val statusProperties: JsonObject = JsonObject(), val statusProperties: JsonObject = JsonObject(),
val minimumLiquidStatusEffectPercentage: Double, val minimumLiquidStatusEffectPercentage: Double = 0.5,
val appliesEnvironmentStatusEffects: Boolean, val appliesEnvironmentStatusEffects: Boolean = true,
val appliesWeatherStatusEffects: Boolean, val appliesWeatherStatusEffects: Boolean = true,
val environmentStatusEffectUpdateTimer: Double = 0.15, val environmentStatusEffectUpdateTimer: Double = 0.15,
val primaryAnimationConfig: AssetPath? = null, val primaryAnimationConfig: AssetPath? = null,
val primaryScriptSources: ImmutableList<AssetPath> = ImmutableList.of(), val primaryScriptSources: ImmutableList<AssetPath> = ImmutableList.of(),
val primaryScriptDelta: Int = 1, val primaryScriptDelta: Double = 1.0,
val keepDamageNotificationSteps: Int = 120, val keepDamageNotificationSteps: Int = 120,
val stats: ImmutableMap<String, Stat> = ImmutableMap.of(), val stats: ImmutableMap<String, Stat> = ImmutableMap.of(),
val resources: ImmutableMap<String, Resource> = ImmutableMap.of(), val resources: ImmutableMap<String, Resource> = ImmutableMap.of(),
) { ) {
init { init {
require(primaryScriptDelta >= 1) { "Non-positive primaryScriptDelta: $primaryScriptDelta" } require(primaryScriptDelta > 0.0) { "Non-positive primaryScriptDelta: $primaryScriptDelta" }
require(keepDamageNotificationSteps >= 1) { "Non-positive keepDamageNotificationSteps: $keepDamageNotificationSteps" } require(keepDamageNotificationSteps >= 1) { "Non-positive keepDamageNotificationSteps: $keepDamageNotificationSteps" }
} }
@ -40,4 +40,8 @@ data class StatusControllerConfig(
val initialValue: Double? = null, val initialValue: Double? = null,
val initialPercentage: Double? = null, val initialPercentage: Double? = null,
) )
companion object {
val EMPTY = StatusControllerConfig()
}
} }

View File

@ -1,7 +1,13 @@
package ru.dbotthepony.kstarbound.defs.actor 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 import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
// stat modifier or named status effect
typealias PersistentStatusEffect = Either<StatModifier, Registry.Ref<StatusEffectDefinition>>
// uint8_t // uint8_t
enum class Gender(override val jsonName: String) : IStringSerializable { enum class Gender(override val jsonName: String) : IStringSerializable {
MALE("Male"), FEMALE("Female"); MALE("Male"), FEMALE("Female");

View File

@ -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 }))
}

View File

@ -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(),
)

View File

@ -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) });
}

View File

@ -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)
)
}
}
}

View File

@ -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))
}
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -38,6 +38,7 @@ import ru.dbotthepony.kstarbound.lua.StateMachine
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.indexNoYield import ru.dbotthepony.kstarbound.lua.indexNoYield
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import java.io.DataInputStream import java.io.DataInputStream
@ -198,7 +199,7 @@ data class ItemDescriptor(
} }
return allocator.newTable(0, 3).also { return allocator.newTable(0, 3).also {
it.rawset("name", name) it.rawset("name", name.toByteString())
it.rawset("count", count) it.rawset("count", count)
it.rawset("parameters", allocator.from(parameters)) it.rawset("parameters", allocator.from(parameters))
} }

View File

@ -1,7 +0,0 @@
package ru.dbotthepony.kstarbound.defs.monster
data class ActionDefinition(
val name: String, // ссылается на .nodes?
val cooldown: Double = -1.0,
// val parameters
)

View File

@ -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>,
)

View File

@ -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(),
)

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.kstarbound.defs.monster package ru.dbotthepony.kstarbound.defs.monster
import com.google.common.collect.ImmutableMap import com.google.gson.JsonObject
import com.google.gson.JsonElement
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@JsonFactory @JsonFactory
data class MonsterSkillDefinition( data class MonsterSkillDefinition(
val name: String, val name: String,
val label: String? = null, val label: String,
val image: SpriteReference? = null, val image: String,
val config: ImmutableMap<String, JsonElement> = ImmutableMap.of(),
val animationParameters: ImmutableMap<String, JsonElement> = ImmutableMap.of(), val config: JsonObject = JsonObject(),
val parameters: JsonObject = JsonObject(),
val animationParameters: JsonObject = JsonObject(),
) )

View File

@ -3,34 +3,285 @@ package ru.dbotthepony.kstarbound.defs.monster
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet 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.kommons.util.Either
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry 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.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.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition 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.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 @JsonFactory
data class MonsterTypeDefinition( data class MonsterTypeDefinition(
val type: String, val type: String,
@JsonFlat val shortdescription: String? = null,
val desc: IThingWithDescription, val description: String? = null,
val categories: ImmutableSet<String> = ImmutableSet.of(), val categories: ImmutableSet<String>,
val parts: ImmutableSet<String> = ImmutableSet.of(), val parts: ImmutableSet<String>,
val animation: AssetReference<AnimationDefinition>, val animation: AssetReference<AnimationDefinition>,
// [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ], val colors: String = "default",
// "dropPools" : [ "smallRobotTreasure" ], val reversed: Boolean = false,
val dropPools: Either<ImmutableList<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>>, ImmutableList<Registry.Ref<TreasurePoolDefinition>>>, val dropPools: ImmutableList<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
val baseParameters: BaseParameters val baseParameters: JsonElement = JsonNull.INSTANCE,
) : IThingWithDescription by desc {
@JsonFactory @Deprecated("Raw property, use processed one", replaceWith = ReplaceWith("this.paramsOverrides"))
data class BaseParameters( val partParameters: JsonElement = JsonNull.INSTANCE,
val movementSettings: ActorMovementParameters? = null,
@JsonFlat @Deprecated("Raw property, use processed one", replaceWith = ReplaceWith("this.paramsDesc"))
val script: IScriptable, val partParameterDescription: JsonElement = JsonObject()
) : IScriptable by script ) {
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
}
} }

View File

@ -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")
}
}
}
}

View File

@ -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
)

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import ru.dbotthepony.kommons.math.RGBAColor 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.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@ -15,7 +16,7 @@ data class AsteroidWorldsConfig(
val gravityRange: Vector2d, val gravityRange: Vector2d,
val worldSize: Vector2i, val worldSize: Vector2i,
val threatRange: Vector2d, val threatRange: Vector2d,
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(), val environmentStatusEffects: ImmutableSet<PersistentStatusEffect> = ImmutableSet.of(),
val overrideTech: ImmutableSet<String>? = null, val overrideTech: ImmutableSet<String>? = null,
val globalDirectives: ImmutableSet<String>? = null, val globalDirectives: ImmutableSet<String>? = null,
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in asteroid field. val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in asteroid field.

View File

@ -21,6 +21,7 @@ data class Biome(
val surfacePlaceables: BiomePlaceables = BiomePlaceables(), val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(), val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
val parallax: Parallax? = null, val parallax: Parallax? = null,
val spawnProfile: SpawnProfile? = null,
) { ) {
@JvmName("hueShiftTile") @JvmName("hueShiftTile")
fun hueShift(block: Registry.Entry<TileDefinition>): Float { fun hueShift(block: Registry.Entry<TileDefinition>): Float {

View File

@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableSet 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.kommons.collect.filterNotNull
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
@ -9,6 +11,7 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.json.NativeLegacy import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@ -21,7 +24,7 @@ import java.util.random.RandomGenerator
data class BiomeDefinition( data class BiomeDefinition(
val airless: Boolean = false, val airless: Boolean = false,
val name: String, 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 weather: ImmutableList<Pair<Double, ImmutableList<AssetReference<WeightedList<String>>>>> = ImmutableList.of(), // binned reference to other assets
val hueShiftOptions: ImmutableList<Double> = ImmutableList.of(), val hueShiftOptions: ImmutableList<Double> = ImmutableList.of(),
val skyOptions: ImmutableList<SkyColoring> = ImmutableList.of(), val skyOptions: ImmutableList<SkyColoring> = ImmutableList.of(),
@ -34,6 +37,7 @@ data class BiomeDefinition(
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(), val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(), val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
val parallax: AssetReference<Parallax.Data>? = null, val parallax: AssetReference<Parallax.Data>? = null,
val spawnProfile: JsonObject? = null,
) { ) {
data class CreationParams( data class CreationParams(
val hueShift: Double, val hueShift: Double,
@ -82,7 +86,9 @@ data class BiomeDefinition(
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second } .map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
.filter { it.first.native.isPresent } .filter { it.first.native.isPresent }
.collect(ImmutableList.toImmutableList()) .collect(ImmutableList.toImmutableList())
}?.orElse(ImmutableList.of()) ?: ImmutableList.of()) }?.orElse(ImmutableList.of()) ?: ImmutableList.of()),
spawnProfile = spawnProfile?.let { SpawnProfile.create(it, random) }
) )
} }

View File

@ -8,6 +8,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.collect.WeightedList import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.AssetPath import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@ -17,7 +18,7 @@ data class DungeonWorldsConfig(
val worldSize: Vector2i, val worldSize: Vector2i,
val gravity: Either<Double, Vector2d>, val gravity: Either<Double, Vector2d>,
val airless: Boolean = false, val airless: Boolean = false,
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(), val environmentStatusEffects: ImmutableSet<PersistentStatusEffect> = ImmutableSet.of(),
val overrideTech: ImmutableSet<String>? = null, val overrideTech: ImmutableSet<String>? = null,
val globalDirectives: ImmutableSet<String>? = null, val globalDirectives: ImmutableSet<String>? = null,
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in floating dungeon. val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in floating dungeon.

View File

@ -438,6 +438,7 @@ data class SkyGlobalConfig(
val starVelocityFactor: Double = 0.0, val starVelocityFactor: Double = 0.0,
val flyingTimer: Double = 0.0, val flyingTimer: Double = 0.0,
val flashTimer: Double = 1.0, val flashTimer: Double = 1.0,
val dayTransitionTime: Double = 200.0,
) { ) {
@JsonFactory @JsonFactory
data class Stars( data class Stars(

View File

@ -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))
}
}

View File

@ -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)
}
}
}

View File

@ -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())
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
}
}

View File

@ -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" }
}
}

View File

@ -540,7 +540,7 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
while (indices.isNotEmpty()) { while (indices.isNotEmpty()) {
val v = indices.random(random) val v = indices.random(random)
shuffled.add(v) shuffled.add(v)
indices.removeInt(indices.indexOf(v)) indices.rem(v)
} }
while (secondaryRegionCount-- > 0 && shuffled.isNotEmpty()) { while (secondaryRegionCount-- > 0 && shuffled.isNotEmpty()) {

View File

@ -23,10 +23,15 @@ import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeCollection import ru.dbotthepony.kommons.io.writeCollection
import ru.dbotthepony.kommons.io.writeStruct2i import ru.dbotthepony.kommons.io.writeStruct2i
import ru.dbotthepony.kommons.util.Either 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.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList 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.fromJson
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.io.readDouble import ru.dbotthepony.kstarbound.io.readDouble
@ -110,7 +115,7 @@ abstract class VisitableWorldParameters {
protected set protected set
var airless: Boolean = false var airless: Boolean = false
protected set protected set
var environmentStatusEffects: Set<String> by Delegates.notNull() var environmentStatusEffects: Set<PersistentStatusEffect> by Delegates.notNull()
protected set protected set
var overrideTech: Set<String>? = null var overrideTech: Set<String>? = null
protected set protected set
@ -140,7 +145,7 @@ abstract class VisitableWorldParameters {
val worldSize: Vector2i, val worldSize: Vector2i,
val gravity: Either<Vector2d, Double>, val gravity: Either<Vector2d, Double>,
val airless: Boolean, val airless: Boolean,
val environmentStatusEffects: Set<String>, val environmentStatusEffects: Set<PersistentStatusEffect>,
val overrideTech: Set<String>? = null, val overrideTech: Set<String>? = null,
val globalDirectives: Set<String>? = null, val globalDirectives: Set<String>? = null,
val beamUpRule: BeamUpRule, val beamUpRule: BeamUpRule,
@ -211,7 +216,7 @@ abstract class VisitableWorldParameters {
if (collection.isNotEmpty()) if (collection.isNotEmpty())
weatherPool = WeightedList(ImmutableList.copyOf(collection)) 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() }) } overrideTech = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
globalDirectives = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) } globalDirectives = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
@ -234,7 +239,7 @@ abstract class VisitableWorldParameters {
else else
stream.writeCollection(weatherPool!!.parent) { writeDouble(it.first); writeBinaryString(it.second) } 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(overrideTech) { writeCollection(it) { writeBinaryString(it) } }
stream.writeNullable(globalDirectives) { writeCollection(it) { writeBinaryString(it) } } stream.writeNullable(globalDirectives) { writeCollection(it) { writeBinaryString(it) } }
stream.writeByte(beamUpRule.ordinal) stream.writeByte(beamUpRule.ordinal)

View File

@ -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.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
class WorldServerConfig( class WorldServerConfig(
val playerSpaceStartRegionSize: Vector2d, val playerSpaceStartRegionSize: Vector2d,

View File

@ -6,11 +6,15 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kommons.util.Either 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.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound 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.dungeon.DungeonDefinition
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.kstarbound.defs.world package ru.dbotthepony.kstarbound.defs.world
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.math.vector.Vector2d
@JsonFactory @JsonFactory
data class WorldTemplateConfig( data class WorldTemplateConfig(
@ -14,4 +16,8 @@ data class WorldTemplateConfig(
val customTerrainBlendSize: Double = 0.0, val customTerrainBlendSize: Double = 0.0,
val surfaceCaveAttenuationDist: Double = 0.0, val surfaceCaveAttenuationDist: Double = 0.0,
val surfaceCaveAttenuationFactor: Double = 1.0, val surfaceCaveAttenuationFactor: Double = 1.0,
)
val defaultGravity: Either<Vector2d, Double>,
) {
val defaultGravityVector = defaultGravity.map({ it }, { Vector2d(0.0, it) })
}

View File

@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.io
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.longs.LongArrayList 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.io.readVarInt
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import java.io.Closeable import java.io.Closeable

View 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)
}
}
}

View File

@ -10,6 +10,7 @@ import ru.dbotthepony.kommons.io.readFloat
import ru.dbotthepony.kommons.io.readInt import ru.dbotthepony.kommons.io.readInt
import ru.dbotthepony.kommons.io.readSignedVarInt import ru.dbotthepony.kommons.io.readSignedVarInt
import ru.dbotthepony.kommons.io.readSignedVarLong import ru.dbotthepony.kommons.io.readSignedVarLong
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeByteArray import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeDouble import ru.dbotthepony.kommons.io.writeDouble
@ -322,3 +323,19 @@ fun InputStream.readDouble(precision: Double, isLegacy: Boolean): Double {
return readDouble() 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)
}

View File

@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.fromJson import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.JsonPatch import ru.dbotthepony.kstarbound.json.JsonPatch
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.fromJsonTreeFast
import java.util.Collections import java.util.Collections
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Future import java.util.concurrent.Future
@ -50,7 +51,6 @@ object ItemRegistry {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val entries = HashMap<String, Entry>() private val entries = HashMap<String, Entry>()
private val tasks = ConcurrentLinkedQueue<Runnable>()
val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null) val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null)
@ -134,9 +134,6 @@ object ItemRegistry {
} }
fun finishLoad() { fun finishLoad() {
tasks.forEach { it.run() }
tasks.clear()
for (obj in Registries.worldObjects.keys.values) { for (obj in Registries.worldObjects.keys.values) {
if (obj.value.hasObjectItem) { if (obj.value.hasObjectItem) {
addObjectItem(obj) addObjectItem(obj)
@ -154,10 +151,10 @@ object ItemRegistry {
for (file in files) { for (file in files) {
futures.add(Starbound.GLOBAL_SCOPE.launch { futures.add(Starbound.GLOBAL_SCOPE.launch {
try { try {
val read = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(file.asyncJsonReader().await()), patches[file.computeFullPath()]).asJsonObject val read = JsonPatch.applyAsync(file.asyncJsonReader(), patches[file.computeFullPath()]).asJsonObject
val readData = data.fromJsonTree(read) val readData = data.fromJsonTreeFast(read)
tasks.add { Starbound.submit {
if (readData.itemName in entries) { if (readData.itemName in entries) {
LOGGER.warn("Overwriting item definition at ${readData.itemName} (old def originate from ${entries[readData.itemName]!!.file}; new from $file)") LOGGER.warn("Overwriting item definition at ${readData.itemName} (old def originate from ${entries[readData.itemName]!!.file}; new from $file)")
} }

View File

@ -58,7 +58,7 @@ import kotlin.properties.Delegates
@JsonAdapter(ItemStack.Adapter::class) @JsonAdapter(ItemStack.Adapter::class)
open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, parameters: JsonObject, size: Long) { 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() var changeset: Long = CHANGESET.incrementAndGet()
private set private set

View File

@ -32,40 +32,6 @@ object RecipeRegistry {
val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking) val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking)
val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking) 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<*>> { fun load(fileTree: Map<String, Collection<IStarboundFile>>, patchTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
val files = fileTree["recipe"] ?: return emptyList() val files = fileTree["recipe"] ?: return emptyList()
@ -75,9 +41,34 @@ object RecipeRegistry {
Starbound.EXECUTOR.submit { Starbound.EXECUTOR.submit {
try { try {
val json = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patchTree[listedFile.computeFullPath()]) val json = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patchTree[listedFile.computeFullPath()])
val value = recipes.fromJsonTree(json) 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) { } catch (err: Throwable) {
LOGGER.error("Loading recipe definition file $listedFile", err) LOGGER.error("Loading recipe definition file $listedFile", err)
} }

View File

@ -5,11 +5,13 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.stream.JsonReader
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kstarbound.IStarboundFile import ru.dbotthepony.kstarbound.IStarboundFile
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import java.util.concurrent.CompletableFuture
enum class JsonPatch(val key: String) { enum class JsonPatch(val key: String) {
TEST("test") { TEST("test") {
@ -165,5 +167,25 @@ enum class JsonPatch(val key: String) {
return base 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
}
} }
} }

View File

@ -1,15 +1,16 @@
package ru.dbotthepony.kstarbound.json.factory package ru.dbotthepony.kstarbound.json.factory
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull 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>?) { override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
if (value == null) { if (value == null) {
out.nullValue() out.nullValue()
@ -30,6 +31,21 @@ class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val ele
if (reader.consumeNull()) if (reader.consumeNull())
return null 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() reader.beginArray()
val builder = ImmutableMap.Builder<K, V>() val builder = ImmutableMap.Builder<K, V>()

View File

@ -31,7 +31,7 @@ class ImmutableCollectionAdapterFactory(val stringInterner: Interner<String> = I
return ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T> return ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T>
} }
return ImmutableArrayMapTypeAdapter( return Immutable2MapTypeAdapter(
gson.getAdapter(TypeToken.get(elementType0)), gson.getAdapter(TypeToken.get(elementType0)),
gson.getAdapter(TypeToken.get(elementType1)) gson.getAdapter(TypeToken.get(elementType1))
) as TypeAdapter<T> ) as TypeAdapter<T>

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.json.factory package ru.dbotthepony.kstarbound.json.factory
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
@ -29,6 +30,12 @@ object RGBAColorTypeAdapter : TypeAdapter<RGBAColor>() {
if (`in`.consumeNull()) if (`in`.consumeNull())
return null 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() `in`.beginArray()
val red = `in`.nextInt() val red = `in`.nextInt()

View File

@ -29,11 +29,13 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
fun ExecutionContext.toVector2i(table: Any): Vector2i { fun ExecutionContext.toVector2i(table: Any): Vector2i {
val x = indexNoYield(table, 1L) val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L) 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 (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") 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 { fun ExecutionContext.toVector2d(table: Any): Vector2d {
val x = indexNoYield(table, 1L) val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L) 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 (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") 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) return Line2d(p0, p1)
} }
fun String?.toByteString(): ByteString? {
return if (this == null) null else ByteString.of(this)
}
fun ExecutionContext.toPoly(table: Table): Poly { fun ExecutionContext.toPoly(table: Table): Poly {
val vertices = ArrayList<Vector2d>() val vertices = ArrayList<Vector2d>()
@ -70,6 +77,7 @@ fun ExecutionContext.toPoly(table: Table): Poly {
fun ExecutionContext.toVector2f(table: Any): Vector2f { fun ExecutionContext.toVector2f(table: Any): Vector2f {
val x = indexNoYield(table, 1L) val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L) 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 (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") 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 y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L) val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L) ?: 255 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 (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") 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 y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L) val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L) 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 (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") 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 y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L) val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L) 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 (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") 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 { fun toJsonFromLua(value: Any?): JsonElement {
return when (value) { return when (value) {
null, is JsonNull -> JsonNull.INSTANCE null, is JsonNull -> JsonNull.INSTANCE
is String -> JsonPrimitive(value) is String -> JsonPrimitive(value.sbIntern())
is ByteString -> JsonPrimitive(value.decode()) is ByteString -> JsonPrimitive(value.decode().sbIntern())
is Number -> JsonPrimitive(value) is Number -> JsonPrimitive(value)
is Boolean -> InternedJsonElementAdapter.of(value) is Boolean -> InternedJsonElementAdapter.of(value)
is Table -> value.toJson() is Table -> value.toJson()
@ -320,6 +331,22 @@ fun TableFactory.from(value: JsonObject): Table {
return data 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 { fun TableFactory.from(value: JsonArray): Table {
val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size()) val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size())

View File

@ -18,6 +18,10 @@ import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction import org.classdump.luna.runtime.LuaFunction
import org.classdump.luna.runtime.UnresolvedControlThrowable 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.max
import kotlin.math.min 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: * to be used in places where we need to "unpack" table, like this:
* *

View File

@ -12,6 +12,8 @@ import org.classdump.luna.Variable
import org.classdump.luna.compiler.CompilerChunkLoader import org.classdump.luna.compiler.CompilerChunkLoader
import org.classdump.luna.compiler.CompilerSettings import org.classdump.luna.compiler.CompilerSettings
import org.classdump.luna.env.RuntimeEnvironments 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.exec.DirectCallExecutor
import org.classdump.luna.impl.DefaultTable import org.classdump.luna.impl.DefaultTable
import org.classdump.luna.impl.NonsuspendableFunctionException 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.lib.Utf8Lib
import org.classdump.luna.load.ChunkFactory import org.classdump.luna.load.ChunkFactory
import org.classdump.luna.runtime.AbstractFunction1 import org.classdump.luna.runtime.AbstractFunction1
import org.classdump.luna.runtime.Coroutine
import org.classdump.luna.runtime.ExecutionContext import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
@ -158,8 +161,6 @@ class LuaEnvironment : StateContext {
globals["print"] = PrintFunction(globals) globals["print"] = PrintFunction(globals)
globals["require"] = LuaRequire()
// why not use _ENV anyway lol // why not use _ENV anyway lol
globals["self"] = newTable() globals["self"] = newTable()
@ -212,6 +213,7 @@ class LuaEnvironment : StateContext {
private val scripts = ObjectArraySet<ChunkFactory>() private val scripts = ObjectArraySet<ChunkFactory>()
private var initCalled = false private var initCalled = false
private val loadedScripts = ObjectArraySet<String>() private val loadedScripts = ObjectArraySet<String>()
val require = LuaRequire()
inner class LuaRequire : AbstractFunction1<ByteString>() { inner class LuaRequire : AbstractFunction1<ByteString>() {
override fun resume(context: ExecutionContext?, suspendedState: Any?) { override fun resume(context: ExecutionContext?, suspendedState: Any?) {
@ -228,13 +230,32 @@ class LuaEnvironment : StateContext {
} }
} }
init {
globals["require"] = require
}
fun attach(script: ChunkFactory) { 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>) { fun attach(scripts: Collection<AssetPath>) {
for (script in scripts) { if (initCalled) {
attach(Starbound.loadScript(script.fullPath)) 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 errorState = true
} }
fun call(fn: Any, vararg args: Any?) = executor.call(this, fn, *args)
fun init(callInit: Boolean = true): Boolean { fun init(callInit: Boolean = true): Boolean {
check(!initCalled) { "Already called init()" } check(!initCalled) { "Already called init()" }
initCalled = true initCalled = true
@ -257,7 +280,7 @@ class LuaEnvironment : StateContext {
try { try {
executor.call(this, script.newInstance(Variable(globals))) executor.call(this, script.newInstance(Variable(globals)))
} catch (err: Throwable) { } catch (err: Throwable) {
errorState = true // errorState = true
LOGGER.error("Failed to attach script to environment", err) LOGGER.error("Failed to attach script to environment", err)
scripts.clear() scripts.clear()
return false return false
@ -273,7 +296,7 @@ class LuaEnvironment : StateContext {
try { try {
executor.call(this, init) executor.call(this, init)
} catch (err: Throwable) { } catch (err: Throwable) {
errorState = true // errorState = true
LOGGER.error("Exception on init()", err) LOGGER.error("Exception on init()", err)
return false return false
} }
@ -284,7 +307,7 @@ class LuaEnvironment : StateContext {
} }
fun invokeGlobal(name: String, vararg arguments: Any?): Array<Any?> { fun invokeGlobal(name: String, vararg arguments: Any?): Array<Any?> {
if (errorState) if (errorState || !initCalled)
return arrayOf() return arrayOf()
val load = globals[name] val load = globals[name]
@ -293,7 +316,7 @@ class LuaEnvironment : StateContext {
return try { return try {
executor.call(this, load, *arguments) executor.call(this, load, *arguments)
} catch (err: Throwable) { } catch (err: Throwable) {
errorState = true // errorState = true
LOGGER.error("Exception while calling global $name", err) LOGGER.error("Exception while calling global $name", err)
arrayOf() arrayOf()
} }

View File

@ -30,15 +30,15 @@ class LuaMessageHandlerComponent(val lua: LuaEnvironment, val nameProvider: () -
private val logPacer = ActionPacer(1, 5) private val logPacer = ActionPacer(1, 5)
fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement { fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement? {
val handler = handlers[message] ?: throw World.MessageCallException("No registered handler for $message") val handler = handlers[message] ?: return null
try { try {
val unpack = arguments.map { lua.from(it) }.toTypedArray() val unpack = arguments.map { lua.from(it) }.toTypedArray()
val result = lua.executor.call(lua, handler, isLocal, *unpack) val result = lua.executor.call(lua, handler, isLocal, *unpack)
if (result.isEmpty()) { if (result.isEmpty()) {
return JsonNull.INSTANCE return null
} else { } else {
return toJsonFromLua(result[0]) return toJsonFromLua(result[0])
} }

View File

@ -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?) { fun update(delta: Double, vararg arguments: Any?) {
if (stepCount == 0.0) if (stepCount == 0.0)
return return

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.luaFunctionArray import ru.dbotthepony.kstarbound.lua.luaFunctionArray
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toAABB import ru.dbotthepony.kstarbound.lua.toAABB
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toColor import ru.dbotthepony.kstarbound.lua.toColor
import ru.dbotthepony.kstarbound.lua.toPoly import ru.dbotthepony.kstarbound.lua.toPoly
import ru.dbotthepony.kstarbound.lua.toVector2d import ru.dbotthepony.kstarbound.lua.toVector2d
@ -71,7 +72,7 @@ fun provideAnimatorBindings(self: Animator, lua: LuaEnvironment) {
callbacks["rotationGroups"] = luaFunction { callbacks["rotationGroups"] = luaFunction {
val groups = self.rotationGroups() val groups = self.rotationGroups()
val keys = newTable(groups.size, 0) 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) returnBuffer.setTo(keys)
} }

View File

@ -6,26 +6,59 @@ import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set 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.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) { 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() val table = lua.newTable()
lua.globals["entity"] = table 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["position"] = luaFunction { returnBuffer.setTo(from(self.position)) }
table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName) } table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName.toByteString()) }
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get()) } table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) }
table["persistent"] = luaFunction { returnBuffer.setTo(self.isPersistent) } table["persistent"] = luaFunction { returnBuffer.setTo(self.isPersistent) }
table["entityInSight"] = luaFunction { TODO() } table["entityInSight"] = luaFunction { target: Number ->
table["isValidTarget"] = luaFunction { TODO() } 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 { table["damageTeam"] = luaFunction {
val result = newTable() val result = newTable()
result["team"] = self.team.get().team result["team"] = self.team.get().team.toLong()
result["type"] = self.type.jsonName result["type"] = self.type.jsonName.toByteString()
returnBuffer.setTo(result) returnBuffer.setTo(result)
} }

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString 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.set
import ru.dbotthepony.kstarbound.lua.tableMapOf import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toLuaInteger import ru.dbotthepony.kstarbound.lua.toLuaInteger
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
@ -125,7 +128,7 @@ private fun registryDef2(registry: Registry<*>): LuaFunction<Any?, *, *, *, *> {
if (def != null) { if (def != null) {
returnBuffer.setTo(newTable(0, 2).also { returnBuffer.setTo(newTable(0, 2).also {
it["path"] = def.file?.computeFullPath() it["path"] = def.file?.computeFullPath().toByteString()
it["config"] = from(def.json) it["config"] = from(def.json)
}) })
} else { } else {
@ -144,7 +147,7 @@ private val recipesForItem = luaFunction { name: ByteString ->
val list = RecipeRegistry.output2recipes[name.decode()] val list = RecipeRegistry.output2recipes[name.decode()]
if (list == null) { if (list == null) {
returnBuffer.setTo(newTable()) returnBuffer.setTo(tableOf())
} else { } else {
returnBuffer.setTo(newTable(list.size, 0).also { returnBuffer.setTo(newTable(list.size, 0).also {
for ((i, v) in list.withIndex()) { 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)) val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) { 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 return
} }
if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) { 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 return
} }
// original engine parity // original engine parity
context.returnBuffer.setTo("") context.returnBuffer.setTo("".toByteString())
} }
private fun materialFootstepSound(context: ExecutionContext, arguments: ArgumentIterator) { 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)) val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) { 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 return
} }
if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) { 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 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 -> private val materialHealth = luaFunction { id: Any ->
@ -227,7 +230,7 @@ private val materialHealth = luaFunction { id: Any ->
} }
private val liquidName = 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 -> private val liquidId = luaFunction { id: Any ->
@ -235,11 +238,11 @@ private val liquidId = luaFunction { id: Any ->
} }
private val techType = 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 -> 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()) } 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 -> 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 -> 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? -> 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 -> 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 -> 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) 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) { fun provideRootBindings(lua: LuaEnvironment) {
val table = lua.newTable() val table = lua.newTable()
lua.globals["root"] = table lua.globals["root"] = table
@ -480,11 +509,11 @@ fun provideRootBindings(lua: LuaEnvironment) {
table["createBiome"] = createBiome table["createBiome"] = createBiome
table["monsterSkillParameter"] = luaStub("monsterSkillParameter") table["monsterSkillParameter"] = monsterSkillParameter
table["monsterParameters"] = luaStub("monsterParameters") table["monsterParameters"] = monsterParameters
table["monsterMovementSettings"] = luaStub("monsterMovementSettings") table["monsterMovementSettings"] = monsterMovementSettings
table["hasTech"] = registryDefExists(Registries.techs) table["hasTech"] = hasTech
table["techType"] = techType table["techType"] = techType
table["techConfig"] = techConfig table["techConfig"] = techConfig
@ -493,7 +522,6 @@ fun provideRootBindings(lua: LuaEnvironment) {
table["collection"] = luaStub("collection") table["collection"] = luaStub("collection")
table["collectables"] = luaStub("collectables") table["collectables"] = luaStub("collectables")
table["elementalResistance"] = luaStub("elementalResistance") table["elementalResistance"] = elementalResistance
table["dungeonMetadata"] = luaStub("dungeonMetadata") table["dungeonMetadata"] = dungeonMetadata
table["behavior"] = luaStub("behavior")
} }

View File

@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toAABB import ru.dbotthepony.kstarbound.lua.toAABB
import ru.dbotthepony.kstarbound.lua.toAABBi import ru.dbotthepony.kstarbound.lua.toAABBi
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.toVector2i import ru.dbotthepony.kstarbound.lua.toVector2i
@ -234,7 +235,7 @@ fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvi
} }
callbacks["fidelity"] = luaFunction { callbacks["fidelity"] = luaFunction {
returnBuffer.setTo("high") returnBuffer.setTo("high".toByteString())
} }
callbacks["setSkyTime"] = luaFunction { newTime: Number -> callbacks["setSkyTime"] = luaFunction { newTime: Number ->
@ -247,7 +248,7 @@ fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvi
} }
callbacks["setUniverseFlag"] = luaFunction { flag: ByteString -> 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 -> callbacks["unsetUniverseFlag"] = luaFunction { flag: ByteString ->

View File

@ -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()
}
}

View File

@ -1,11 +1,14 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonElement
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString import org.classdump.luna.ByteString
import org.classdump.luna.Table import org.classdump.luna.Table
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.json.JsonPath
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get 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.luaFunctionN
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
@ -67,7 +72,7 @@ private val interpolateSinEase = luaFunctionArray { args ->
} }
private val replaceTags = luaFunction { string: ByteString, tags: Table -> 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 -> private val makePerlinSource = luaFunction { settings: Table ->
@ -94,15 +99,16 @@ private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining())) returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
} }
fun provideUtilityBindings( private val mergeJson = luaFunction { a: Any?, b: Any? ->
lua: LuaEnvironment, returnBuffer.setTo(from(ru.dbotthepony.kstarbound.json.mergeJson(toJsonFromLua(a), toJsonFromLua(b))))
random: RandomGenerator = random() }
) {
fun provideUtilityBindings(lua: LuaEnvironment) {
val table = lua.newTable() val table = lua.newTable()
lua.globals["sb"] = table lua.globals["sb"] = table
table["makeUuid"] = luaFunction { 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 table["logInfo"] = logInfo
@ -112,7 +118,7 @@ fun provideUtilityBindings(
table["nrand"] = luaFunctionN("nrand") { args -> table["nrand"] = luaFunctionN("nrand") { args ->
val stdev = args.nextOptionalFloat() ?: 1.0 val stdev = args.nextOptionalFloat() ?: 1.0
val mean = args.nextOptionalFloat() ?: 0.0 val mean = args.nextOptionalFloat() ?: 0.0
random.nextNormalDouble(stdev, mean) lua.random.nextNormalDouble(stdev, mean)
} }
table["print"] = lua.globals["tostring"] table["print"] = lua.globals["tostring"]
@ -120,7 +126,7 @@ fun provideUtilityBindings(
table["interpolateSinEase"] = interpolateSinEase table["interpolateSinEase"] = interpolateSinEase
table["replaceTags"] = replaceTags table["replaceTags"] = replaceTags
table["makeRandomSource"] = luaFunction { seed: Long? -> table["makeRandomSource"] = luaFunction { seed: Long? ->
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: random.nextLong()))) returnBuffer.setTo(LuaRandomGenerator(random(seed ?: lua.random.nextLong())))
} }
table["makePerlinSource"] = makePerlinSource table["makePerlinSource"] = makePerlinSource
@ -132,4 +138,21 @@ fun provideUtilityBindings(
table["staticRandomDoubleRange"] = staticRandomDoubleRange table["staticRandomDoubleRange"] = staticRandomDoubleRange
table["staticRandomI32Range"] = staticRandomI32Range table["staticRandomI32Range"] = staticRandomI32Range
table["staticRandomI64Range"] = 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)
} }

View File

@ -12,12 +12,15 @@ import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.toList import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kstarbound.Registries 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.EntityType
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.lua.LuaEnvironment 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.set
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toAABB import ru.dbotthepony.kstarbound.lua.toAABB
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toLine2d 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.toVector2i
import ru.dbotthepony.kstarbound.lua.unpackAsArray import ru.dbotthepony.kstarbound.lua.unpackAsArray
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture 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.AABB
import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kstarbound.util.CarriedExecutor
import ru.dbotthepony.kstarbound.util.GameTimer import ru.dbotthepony.kstarbound.util.GameTimer
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.util.random.shuffle 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.castRay
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity 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.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.world.physics.CollisionType
@ -82,7 +89,7 @@ private fun ExecutionContext.resolvePolyCollision(self: World<*, *>, originalPol
val tiles = ObjectArrayList<Entry>() 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) { if (tile.type in collisions) {
tiles.add(Entry(tile.poly, tile.poly.centre)) 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 -> callbacks["polyContains"] = luaFunction { poly: Table, position: Table ->
returnBuffer.setTo(self.geometry.polyContains(toPoly(poly), toVector2d(position))) 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? -> callbacks["rectCollision"] = luaFunction { rect: Table, collisions: Table? ->
if (collisions == null) { 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 { } else {
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList()) 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? -> callbacks["pointCollision"] = luaFunction { rect: Table, collisions: Table? ->
if (collisions == null) { 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 { } else {
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList()) 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? -> callbacks["rectTileCollision"] = luaFunction { rect: Table, collisions: Table? ->
if (collisions == null) { 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 { } else {
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList()) 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? -> 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 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) { if (result == null) {
returnBuffer.setTo() returnBuffer.setTo()
@ -291,10 +303,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
callbacks["polyCollision"] = luaFunction { rect: Table, translate: Table?, collisions: Table? -> callbacks["polyCollision"] = luaFunction { rect: Table, translate: Table?, collisions: Table? ->
if (collisions == null) { 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 { } else {
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList()) 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 { callbacks["spawnMonster"] = luaFunction {
// TODO // TODO
returnBuffer.setTo(0) returnBuffer.setTo(0L)
} }
callbacks["spawnNpc"] = luaStub("spawnNpc") callbacks["spawnNpc"] = luaStub("spawnNpc")
callbacks["spawnStagehand"] = luaStub("spawnStagehand") callbacks["spawnStagehand"] = luaStub("spawnStagehand")
@ -483,7 +495,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
} }
callbacks["liquidAt"] = luaFunction { posOrRect: Table -> callbacks["liquidAt"] = luaFunction { posOrRect: Table ->
if (posOrRect[1L] is Number) { if (posOrRect[3L] !is Number) {
val cell = self.getCell(toVector2i(posOrRect)) val cell = self.getCell(toVector2i(posOrRect))
if (cell.liquid.state.isNotEmptyLiquid) { if (cell.liquid.state.isNotEmptyLiquid) {
@ -499,7 +511,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
} }
callbacks["liquidNameAt"] = luaFunction { posOrRect: Table -> callbacks["liquidNameAt"] = luaFunction { posOrRect: Table ->
if (posOrRect[1L] is Number) { if (posOrRect[3L] !is Number) {
val cell = self.getCell(toVector2i(posOrRect)) val cell = self.getCell(toVector2i(posOrRect))
if (cell.liquid.state.isNotEmptyLiquid) { if (cell.liquid.state.isNotEmptyLiquid) {
@ -515,11 +527,11 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
} }
callbacks["gravity"] = luaFunction { pos: Table -> 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 -> 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 -> 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["isTileProtected"] = luaFunction { pos: Table -> returnBuffer.setTo(self.isDungeonIDProtected(self.getCell(toVector2i(pos)).dungeonId)) }
callbacks["findPlatformerPath"] = luaStub("findPlatformerPath") callbacks["findPlatformerPath"] = luaFunction { start: Table, end: Table, actorParams: Table, searchParams: Table ->
callbacks["platformerPathStart"] = luaStub("platformerPathStart") 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["size"] = luaFunction { returnBuffer.setTo(from(self.geometry.size)) }
callbacks["inSurfaceLayer"] = luaFunction { pos: Table -> returnBuffer.setTo(self.template.isSurfaceLayer(toVector2i(pos))) } callbacks["inSurfaceLayer"] = luaFunction { pos: Table -> returnBuffer.setTo(self.template.isSurfaceLayer(toVector2i(pos))) }
callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel) } callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel) }
@ -610,4 +644,10 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
if (self is ServerWorld) { if (self is ServerWorld) {
provideServerWorldBindings(self, callbacks, lua) provideServerWorldBindings(self, callbacks, lua)
} }
// TODO
callbacks["debugPoint"] = luaFunction { }
callbacks["debugLine"] = luaFunction { }
callbacks["debugPoly"] = luaFunction { }
callbacks["debugText"] = luaFunction { }
} }

View File

@ -22,6 +22,7 @@ import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toAABB import ru.dbotthepony.kstarbound.lua.toAABB
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toLine2d import ru.dbotthepony.kstarbound.lua.toLine2d
import ru.dbotthepony.kstarbound.lua.toPoly import ru.dbotthepony.kstarbound.lua.toPoly
@ -345,12 +346,12 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
callbacks["entitySpecies"] = luaFunction { id: Number -> callbacks["entitySpecies"] = luaFunction { id: Number ->
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo() 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 -> callbacks["entityGender"] = luaFunction { id: Number ->
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo() 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 -> callbacks["entityName"] = luaFunction { id: Number ->
@ -358,8 +359,8 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
// TODO // TODO
when (entity) { when (entity) {
is ActorEntity -> returnBuffer.setTo(entity.name) is ActorEntity -> returnBuffer.setTo(entity.name.toByteString())
is WorldObject -> returnBuffer.setTo(entity.config.key) 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() val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
when (val gethand = hand.decode().lowercase()) { when (val gethand = hand.decode().lowercase()) {
"primary" -> returnBuffer.setTo(entity.primaryHandItem.entry.nameOrNull) "primary" -> returnBuffer.setTo(entity.primaryHandItem.entry.nameOrNull.toByteString())
"alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.entry.nameOrNull) "alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.entry.nameOrNull.toByteString())
else -> throw LuaRuntimeException("Unknown tool hand $gethand") else -> throw LuaRuntimeException("Unknown tool hand $gethand")
} }
} }

View File

@ -8,6 +8,7 @@ import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.toList import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType 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.set
import ru.dbotthepony.kstarbound.lua.tableFrom import ru.dbotthepony.kstarbound.lua.tableFrom
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toVector2d import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.toVector2i import ru.dbotthepony.kstarbound.lua.toVector2i
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
@ -107,7 +109,7 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
callbacks["windLevel"] = luaStub("windLevel") callbacks["windLevel"] = luaStub("windLevel")
callbacks["breathable"] = luaFunction { pos: Table -> callbacks["breathable"] = luaFunction { pos: Table ->
returnBuffer.setTo(self.isBreathable(toVector2i(pos))) returnBuffer.setTo(self.chunkMap.isBreathable(toVector2i(pos)))
} }
callbacks["underground"] = luaFunction { pos: Table -> callbacks["underground"] = luaFunction { pos: Table ->
@ -123,7 +125,7 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua:
} else if (tile.material.isEmptyTile) { } else if (tile.material.isEmptyTile) {
returnBuffer.setTo(false) returnBuffer.setTo(false)
} else { } 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) val tile = self.getCell(toVector2i(pos)).tile(isBackground)
if (tile.modifier.isNotEmptyModifier) { if (tile.modifier.isNotEmptyModifier) {
returnBuffer.setTo(tile.modifier.key) returnBuffer.setTo(tile.modifier.key.toByteString())
} }
} }
callbacks["materialHueShift"] = luaFunction { pos: Table, layer: ByteString -> callbacks["materialHueShift"] = luaFunction { pos: Table, layer: ByteString ->
val isBackground = isBackground(layer) val isBackground = isBackground(layer)
val tile = self.getCell(toVector2i(pos)).tile(isBackground) val tile = self.getCell(toVector2i(pos)).tile(isBackground)
returnBuffer.setTo(tile.hueShift) returnBuffer.setTo(tile.hueShift.toDouble())
} }
callbacks["modHueShift"] = luaFunction { pos: Table, layer: ByteString -> callbacks["modHueShift"] = luaFunction { pos: Table, layer: ByteString ->
val isBackground = isBackground(layer) val isBackground = isBackground(layer)
val tile = self.getCell(toVector2i(pos)).tile(isBackground) val tile = self.getCell(toVector2i(pos)).tile(isBackground)
returnBuffer.setTo(tile.modifierHueShift) returnBuffer.setTo(tile.modifierHueShift.toDouble())
} }
callbacks["materialColor"] = luaFunction { pos: Table, layer: ByteString -> callbacks["materialColor"] = luaFunction { pos: Table, layer: ByteString ->
val isBackground = isBackground(layer) val isBackground = isBackground(layer)
val tile = self.getCell(toVector2i(pos)).tile(isBackground) 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 -> callbacks["materialColorName"] = luaFunction { pos: Table, layer: ByteString ->
val isBackground = isBackground(layer) val isBackground = isBackground(layer)
val tile = self.getCell(toVector2i(pos)).tile(isBackground) 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 -> 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 -> 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 -> 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") { callbacks["damageTiles"] = luaFunctionN("damageTiles") {

View File

@ -21,12 +21,14 @@ import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toColor import ru.dbotthepony.kstarbound.lua.toColor
import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.toVector2i import ru.dbotthepony.kstarbound.lua.toVector2i
import ru.dbotthepony.kstarbound.util.SBPattern 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.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
@ -48,12 +50,12 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
val table = lua.newTable() val table = lua.newTable()
lua.globals["object"] = table 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["direction"] = luaFunction { returnBuffer.setTo(self.direction.luaValue) }
table["position"] = luaFunction { returnBuffer.setTo(from(self.tilePosition)) } table["position"] = luaFunction { returnBuffer.setTo(from(self.tilePosition)) }
table["setInteractive"] = luaFunction { interactive: Boolean -> self.isInteractive = interactive } table["setInteractive"] = luaFunction { interactive: Boolean -> self.isInteractive = interactive }
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get()) } table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) }
table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(id?.decode()) } table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(id?.decode()?.sbIntern()) }
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) } table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
// original engine parity, it returns occupied spaces in local coordinates // 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)) returnBuffer.setTo(from(self.lightSourceColor))
} }
table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size) } table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size.toLong()) }
table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size) } table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size.toLong()) }
table["getInputNodePosition"] = luaFunction { index: Long -> table["getInputNodePosition"] = luaFunction { index: Long ->
returnBuffer.setTo(from(self.inputNodes[index.toInt()].position)) returnBuffer.setTo(from(self.inputNodes[index.toInt()].position))

View File

@ -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))
}
)
}
}
}

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.userdata
import org.classdump.luna.Table import org.classdump.luna.Table
import org.classdump.luna.Userdata import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable import org.classdump.luna.impl.ImmutableTable
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@ -32,7 +33,12 @@ class LuaFuture(val future: CompletableFuture<out Any?>, val isLocal: Boolean) :
} }
companion object { companion object {
private fun __index(): Table {
return metadata
}
private val metadata = ImmutableTable.Builder() private val metadata = ImmutableTable.Builder()
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
.add("finished", luaFunction { self: LuaFuture -> .add("finished", luaFunction { self: LuaFuture ->
returnBuffer.setTo(self.future.isDone) returnBuffer.setTo(self.future.isDone)
}) })

View File

@ -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()
}
}

View File

@ -3,7 +3,9 @@ package ru.dbotthepony.kstarbound.lua.userdata
import org.classdump.luna.Table import org.classdump.luna.Table
import org.classdump.luna.Userdata import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable import org.classdump.luna.impl.ImmutableTable
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder.Companion
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNoise>() { class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNoise>() {
@ -28,7 +30,12 @@ class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNo
} }
companion object { companion object {
private fun __index(): Table {
return metatable
}
private val metatable = ImmutableTable.Builder() 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? -> .add("get", luaFunction { self: LuaPerlinNoise, x: Number, y: Number?, z: Number? ->
if (y != null && z != null) { if (y != null && z != null) {
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble(), z.toDouble()]) returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble(), z.toDouble()])

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.userdata
import org.classdump.luna.Table import org.classdump.luna.Table
import org.classdump.luna.Userdata import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable import org.classdump.luna.impl.ImmutableTable
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
@ -63,7 +64,12 @@ class LuaRandomGenerator(var random: RandomGenerator) : Userdata<RandomGenerator
} }
companion object { companion object {
private fun __index(): Table {
return metatable
}
private val metatable = ImmutableTable.Builder() private val metatable = ImmutableTable.Builder()
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
.add("init", luaFunction { self: LuaRandomGenerator, seed: Long? -> .add("init", luaFunction { self: LuaRandomGenerator, seed: Long? ->
self.random = random(seed ?: System.nanoTime()) self.random = random(seed ?: System.nanoTime())
}) })

View File

@ -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 times(other: Vector2d) = AABB(mins * other, maxs * other)
operator fun div(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 times(other: Double) = AABB(mins * other, maxs * other)
operator fun div(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 expand(value: IStruct2d) = expand(value.component1(), value.component2())
fun padded(value: IStruct2d) = expand(value.component1(), value.component2()) fun padded(value: IStruct2d) = padded(value.component1(), value.component2())
fun padded(x: Double, y: Double) = expand(x, y)
/** /**
* Returns AABB which edges are expanded by [x] and [y] along their normals * 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 ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)) @JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
} }

View File

@ -4,6 +4,7 @@ import ru.dbotthepony.kommons.math.intersectRectangles
import ru.dbotthepony.kommons.math.rectangleContainsRectangle import ru.dbotthepony.kommons.math.rectangleContainsRectangle
import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import kotlin.math.roundToInt
data class AABBi(val mins: Vector2i, val maxs: Vector2i) { data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
init { 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(x: Int, y: Int): AABBi {
fun padded(value: IStruct2i) = expand(value.component1(), value.component2()) 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()) 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) @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()),
)
}
} }
} }

View File

@ -69,6 +69,9 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
val difference: Vector2d val difference: Vector2d
get() = p1 - p0 get() = p1 - p0
val direction: Vector2d
get() = difference.unitVector
fun reverse(): Line2d { fun reverse(): Line2d {
return Line2d(p1, p0) 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 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 { fun distanceTo(other: IStruct2d, infinite: Boolean = false): Double {
var proj = project(other) var proj = project(other)

View File

@ -186,6 +186,9 @@ data class Vector2d(
fun coerceAtMost(value: Vector2d): Vector2d { val (x, y) = value; return coerceAtMost(x, y) } fun coerceAtMost(value: Vector2d): Vector2d { val (x, y) = value; return coerceAtMost(x, y) }
fun rotate(angle: Double): Vector2d { fun rotate(angle: Double): Vector2d {
if (angle == 0.0)
return this
val s = sin(angle) val s = sin(angle)
val c = cos(angle) val c = cos(angle)
@ -193,6 +196,9 @@ data class Vector2d(
} }
fun toAngle(zeroAngle: Vector2d): Double { 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) val dot = unitVector.dot(zeroAngle.unitVector)
return if (y > 0.0) { return if (y > 0.0) {

View File

@ -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.ChunkCellsPacket
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket 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.ClientContextUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
@ -399,7 +398,6 @@ class PacketRegistry(val isLegacy: Boolean) {
NATIVE.add(::JoinWorldPacket) NATIVE.add(::JoinWorldPacket)
NATIVE.add(::ChunkCellsPacket) NATIVE.add(::ChunkCellsPacket)
NATIVE.add(::ForgetChunkPacket) NATIVE.add(::ForgetChunkPacket)
NATIVE.add(::SpawnWorldObjectPacket)
NATIVE.add(::ForgetEntityPacket) NATIVE.add(::ForgetEntityPacket)
NATIVE.add(::UniverseTimeUpdatePacket) NATIVE.add(::UniverseTimeUpdatePacket)
NATIVE.add(::StepUpdatePacket) NATIVE.add(::StepUpdatePacket)

View File

@ -4,9 +4,7 @@ import it.unimi.dsi.fastutil.bytes.ByteArrayList
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.io.readByteArray import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readByteKey
import ru.dbotthepony.kommons.io.readCollection import ru.dbotthepony.kommons.io.readCollection
import ru.dbotthepony.kommons.io.readKOptional import ru.dbotthepony.kommons.io.readKOptional
import ru.dbotthepony.kommons.io.readMap 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.io.writeVarInt
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.client.ClientConnection 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.json.readJsonElement
import ru.dbotthepony.kstarbound.network.ConnectionSide import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket

View File

@ -1,21 +1,21 @@
package ru.dbotthepony.kstarbound.network.packets.serverbound package ru.dbotthepony.kstarbound.network.packets.serverbound
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.io.readBinaryString import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readByteArray import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readByteKey
import ru.dbotthepony.kommons.io.readKOptional import ru.dbotthepony.kommons.io.readKOptional
import ru.dbotthepony.kommons.io.readMap import ru.dbotthepony.kommons.io.readMap
import ru.dbotthepony.kommons.io.readUUID import ru.dbotthepony.kommons.io.readUUID
import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeByteArray import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeByteKey
import ru.dbotthepony.kommons.io.writeKOptional import ru.dbotthepony.kommons.io.writeKOptional
import ru.dbotthepony.kommons.io.writeMap import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kommons.io.writeUUID import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades 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.IServerPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket

View File

@ -42,11 +42,16 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
val old = value val old = value
value = t value = t
queue.clear() queue.clear()
bumpVersion() super.bumpVersion()
valueListeners.forEach { it.callable.invoke(t, old) } 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 { override fun addListener(listener: Consumer<TYPE>): Listenable.L {
return Listener(listener) return Listener(listener)
} }
@ -68,7 +73,7 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
val old = value val old = value
value = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data) value = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
queue.clear() queue.clear()
bumpVersion() super.bumpVersion()
if (value != old) { if (value != old) {
valueListeners.forEach { it.callable.invoke(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) { override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
val read = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data) val read = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
bumpVersion() super.bumpVersion()
if (isInterpolating) { if (isInterpolating) {
queue.push(read, interpolationDelay) queue.push(read, interpolationDelay)

View File

@ -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 val listeners = CopyOnWriteArrayList<Listener>()
private inner class Listener(val listener: ListenableMap.MapListener<K, V>) : Listenable.L { private inner class Listener(val listener: ListenableMap.MapListener<K, V>) : Listenable.L {

View File

@ -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) { fun handle(source: ServerConnection, packet: ChatSendPacket) {
when (packet.mode) { when (packet.mode) {
ChatSendMode.BROADCAST -> { ChatSendMode.BROADCAST -> {

View File

@ -12,11 +12,7 @@ class IntegratedStarboundServer(val client: StarboundClient, root: File) : Starb
override fun tick0(delta: Double) { override fun tick0(delta: Double) {
if (client.isShutdown) { if (client.isShutdown) {
shutdown() close()
} }
} }
override fun close0() {
}
} }

View File

@ -17,6 +17,7 @@ import java.io.Closeable
import java.net.SocketAddress import java.net.SocketAddress
import java.util.* import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock 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>() { val channel = ServerBootstrap().channel(LocalServerChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
override fun initChannel(ch: 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 { try {
val connection = ServerConnection(server, ConnectionType.MEMORY) val connection = ServerConnection(server, ConnectionType.MEMORY)
connections.add(connection) connections.add(connection)
connection.bind(ch) connection.bind(ch)
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err) LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
ch.close() ch.close()
}
} }
} }
}).bind(LocalAddress.ANY).syncUninterruptibly() }).bind(LocalAddress.ANY).syncUninterruptibly()
@ -141,15 +145,18 @@ class ServerChannels(val server: StarboundServer) : Closeable {
lock.withLock { lock.withLock {
val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() { val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
override fun initChannel(ch: 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 { try {
val connection = ServerConnection(server, ConnectionType.NETWORK) val connection = ServerConnection(server, ConnectionType.NETWORK)
connections.add(connection) connections.add(connection)
connection.bind(ch) connection.bind(ch)
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err) LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err)
ch.close() ch.close()
}
} }
} }
}).bind(localAddress).syncUninterruptibly() }).bind(localAddress).syncUninterruptibly()
@ -168,9 +175,18 @@ class ServerChannels(val server: StarboundServer) : Closeable {
override fun close() { override fun close() {
lock.withLock { lock.withLock {
if (isClosed) return if (isClosed) return
isClosed = true
connections.forEach { it.disconnect("Server shutting down") }
channels.forEach { it.channel().close() } 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() channels.clear()
connections.clear() connections.clear()
} }

Some files were not shown because too many files have changed in this diff Show More