Working name generator

This commit is contained in:
DBotThePony 2024-04-18 20:47:17 +07:00
parent 432f77a676
commit 7857b8821e
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 212 additions and 6 deletions

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
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.gson.TypeAdapter import com.google.gson.TypeAdapter
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
@ -24,6 +26,7 @@ import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
import ru.dbotthepony.kstarbound.defs.world.SystemWorldConfig 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.mapAdapter 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
@ -107,6 +110,32 @@ object Globals {
var itemParameters by Delegates.notNull<ItemGlobalConfig>() var itemParameters by Delegates.notNull<ItemGlobalConfig>()
private set private set
private var profanityFilterInternal by Delegates.notNull<ImmutableList<String>>()
val profanityFilter: ImmutableSet<String> by lazy {
// reverse "encryption"
val words = ImmutableSet.Builder<String>()
for (word in profanityFilterInternal) {
val chars = CharArray(word.length)
for (i in word.indices) {
var c = word[i]
if ((c >= 'a' + 13 && c <= 'm' + 13) || (c >= 'A' + 13 && c <= 'M' + 13))
c -= 13
else if ((c >= 'n' - 13 && c <= 'z' - 13) || (c >= 'N' - 13 && c <= 'Z' - 13))
c += 13
chars[i] = c
}
words.add(String(chars))
}
words.build()
}
private fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: Lazy<TypeAdapter<T>>): Future<*> { private fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: Lazy<TypeAdapter<T>>): Future<*> {
val file = Starbound.loadJsonAsset(path) val file = Starbound.loadJsonAsset(path)
@ -161,6 +190,8 @@ object Globals {
tasks.add(load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() })) tasks.add(load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }))
tasks.add(load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() })) tasks.add(load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }))
tasks.add(load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }))
return tasks return tasks
} }
} }

View File

@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager
import org.lwjgl.Version import org.lwjgl.Version
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
import ru.dbotthepony.kstarbound.util.random.random
import java.io.File import java.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
@ -27,5 +28,5 @@ fun main() {
Starbound.initializeGame().thenApply { Starbound.initializeGame().thenApply {
val server = IntegratedStarboundServer(client, File("./")) val server = IntegratedStarboundServer(client, File("./"))
server.channels.createChannel(InetSocketAddress(21060)) server.channels.createChannel(InetSocketAddress(21060))
} }.exceptionally { LOGGER.error("what", it); null }
} }

View File

@ -15,6 +15,7 @@ import ru.dbotthepony.kstarbound.defs.AssetReference
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
import ru.dbotthepony.kstarbound.defs.MarkovTextGenerator
import ru.dbotthepony.kstarbound.defs.Species import ru.dbotthepony.kstarbound.defs.Species
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
import ru.dbotthepony.kstarbound.defs.ThingDescription import ru.dbotthepony.kstarbound.defs.ThingDescription
@ -85,6 +86,7 @@ object Registries {
val treeFoliageVariants = Registry<TreeVariant.FoliageData>("tree foliage variant").also(registriesInternal::add).also { adapters.add(it.adapter()) } val treeFoliageVariants = Registry<TreeVariant.FoliageData>("tree foliage 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 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()) }
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() }
@ -170,6 +172,7 @@ object Registries {
tasks.addAll(loadRegistry(treeStemVariants, patchTree, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name))) tasks.addAll(loadRegistry(treeStemVariants, patchTree, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name)))
tasks.addAll(loadRegistry(treeFoliageVariants, patchTree, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::name))) tasks.addAll(loadRegistry(treeFoliageVariants, patchTree, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::name)))
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(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))

View File

@ -27,11 +27,11 @@ import ru.dbotthepony.kommons.gson.Vector4fTypeAdapter
import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.collect.WeightedList import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
import ru.dbotthepony.kstarbound.defs.actor.player.BlueprintLearnList import ru.dbotthepony.kstarbound.defs.actor.player.BlueprintLearnList
import ru.dbotthepony.kstarbound.defs.animation.Particle import ru.dbotthepony.kstarbound.defs.animation.Particle
import ru.dbotthepony.kstarbound.defs.quest.QuestParameter import ru.dbotthepony.kstarbound.defs.quest.QuestParameter
@ -69,12 +69,13 @@ import ru.dbotthepony.kstarbound.util.Directives
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.HashTableInterner import ru.dbotthepony.kstarbound.util.HashTableInterner
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.util.random.nextRange
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.* import java.io.*
import java.lang.ref.Cleaner import java.lang.ref.Cleaner
import java.text.DateFormat import java.text.DateFormat
import java.time.Duration import java.time.Duration
import java.util.Collections
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor import java.util.concurrent.Executor
@ -87,6 +88,7 @@ import java.util.concurrent.ThreadPoolExecutor
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.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.random.RandomGenerator
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -639,5 +641,82 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
} }
} }
} }
private fun processNameRules(rules: JsonArray, random: RandomGenerator): String {
if (rules.isEmpty)
return ""
val meta: JsonObject
val result = StringBuilder()
var mode = "alts"
var index = 0
var uppercase = false
if (rules[0] is JsonObject) {
meta = rules[0] as JsonObject
mode = meta.get("mode", mode)
uppercase = meta.get("titleCase", false)
index++
} else {
meta = JsonObject()
}
when (mode) {
"serie" -> {
while (index < rules.size()) {
val entry = rules[index++]
if (entry is JsonArray) {
result.append(processNameRules(entry, random))
} else {
result.append(entry.asString)
}
}
}
"alts" -> {
val i = if (rules.size() == 1) throw RuntimeException("Николай не протоген") else random.nextInt(index, rules.size())
val entry = rules[i]
if (entry is JsonArray) {
result.append(processNameRules(entry, random))
} else {
result.append(entry.asString)
}
}
"markov" -> {
val source = Registries.markovGenerators.getOrThrow(meta.get("source").asString).value
val lengthRange = gson.fromJson(meta.get("targetLength"), Vector2i::class.java)
val targetLength = random.nextRange(lengthRange)
result.append(source.generate(random, targetLength, lengthRange.y))
}
else -> throw IllegalArgumentException("Unknown name rule mode: $mode")
}
if (uppercase) {
return result.toString().uppercase()
}
return result.toString()
}
fun generateName(asset: String, random: RandomGenerator): String {
val load = loadJsonAsset(asset) as? JsonArray ?: return "missingasset"
var tries = 500
var result = ""
while (tries-- > 0 && (result.isEmpty() || result.lowercase() in Globals.profanityFilter))
result = processNameRules(load, random)
return result
}
fun generateName(asset: String, seed: Long) = generateName(asset, random(seed))
fun generateName(asset: String) = generateName(asset, System.nanoTime())
} }

View File

@ -0,0 +1,84 @@
package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.random.random
import java.util.random.RandomGenerator
@JsonFactory
data class MarkovTextGenerator(
val name: String,
val prefixSize: Int = 1,
val endSize: Int = 1,
val sourceNames: ImmutableList<String>,
) {
val ends: ImmutableList<String>
val starts: ImmutableList<String>
val chains: ImmutableMap<String, ImmutableList<String>>
init {
require(prefixSize > 0) { "Invalid prefix size: $prefixSize" }
require(endSize > 0) { "Invalid suffix size: $endSize" }
val ends = ObjectArraySet<String>()
val starts = ObjectArraySet<String>()
val chains = Object2ObjectArrayMap<String, ObjectArraySet<String>>()
for (sourceName in sourceNames) {
if (sourceName.length < prefixSize || sourceName.length < endSize) {
LOGGER.warn("Name $sourceName is too short for Markov name generator with prefix size of $prefixSize and suffix size $endSize; it will be ignored (generator: ${AssetPathStack.remap(name)})")
continue
}
val sourceName = sourceName.lowercase()
ends.add(sourceName.substring(sourceName.length - endSize, sourceName.length))
for (i in 0 .. sourceName.length - prefixSize) {
val prefix = sourceName.substring(i, i + prefixSize)
if (i == 0)
starts.add(prefix)
if (i + prefixSize < sourceName.length) {
chains
.computeIfAbsent(prefix, Object2ObjectFunction { ObjectArraySet() })
.add(sourceName[i + prefixSize].toString())
}
}
}
this.ends = ImmutableList.copyOf(ends)
this.starts = ImmutableList.copyOf(starts)
this.chains = chains.entries.stream().map { it.key to ImmutableList.copyOf(it.value) }.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
}
fun generate(random: RandomGenerator, targetLength: Int, maxLength: Int = targetLength, maxTries: Int = 50): String {
var tries = 0
var piece: String
do {
piece = starts.random(random)
while (
piece.length < targetLength ||
piece.substring(piece.length - endSize, piece.length) !in ends
) {
val link = piece.substring(piece.length - endSize, piece.length)
piece += (chains[link] ?: break).random(random)
}
} while (tries++ < maxTries && (piece.length > maxLength || piece in Globals.profanityFilter))
return piece
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -207,7 +207,7 @@ data class ItemDescriptor(
lua.random = random ?: lua.random lua.random = random ?: lua.random
lua.init(false) lua.init(false)
val (config, parameters) = lua.invokeGlobal("build", ref.directory, lua.from(ref.json), lua.from(parameters), level, seed) val (config, parameters) = lua.invokeGlobal("build", ref.directory + "/", lua.from(ref.json), lua.from(parameters), level, seed)
val jConfig = toJsonFromLua(config).asJsonObject val jConfig = toJsonFromLua(config).asJsonObject
val jParameters = toJsonFromLua(parameters).asJsonObject val jParameters = toJsonFromLua(parameters).asJsonObject

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.Registries
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.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.lua.LUA_HINT_ARRAY import ru.dbotthepony.kstarbound.lua.LUA_HINT_ARRAY
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaEnvironment
@ -31,7 +32,9 @@ import ru.dbotthepony.kstarbound.lua.luaStub
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
import ru.dbotthepony.kstarbound.lua.nextOptionalInteger 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.toLuaInteger import ru.dbotthepony.kstarbound.lua.toLuaInteger
import ru.dbotthepony.kstarbound.util.random.random
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.isNotEmpty import kotlin.collections.isNotEmpty
@ -71,7 +74,9 @@ private fun evalFunction2(context: ExecutionContext, name: ByteString, value: Do
} }
private fun imageSize(context: ExecutionContext, name: ByteString) { private fun imageSize(context: ExecutionContext, name: ByteString) {
context.returnBuffer.setTo(Image.get(name.decode())?.size ?: throw LuaRuntimeException("No such image $name")) val ref = SpriteReference.create(name.decode())
val sprite = ref.sprite ?: throw LuaRuntimeException("No such image or sprite $ref")
context.returnBuffer.setTo(context.tableOf(sprite.width, sprite.height))
} }
private fun imageSpaces(context: ExecutionContext, arguments: ArgumentIterator): StateMachine { private fun imageSpaces(context: ExecutionContext, arguments: ArgumentIterator): StateMachine {
@ -373,7 +378,10 @@ fun provideRootBindings(lua: LuaEnvironment) {
table["getMatchingTenants"] = luaFunction(::getMatchingTenants) table["getMatchingTenants"] = luaFunction(::getMatchingTenants)
table["liquidStatusEffects"] = luaFunction(::liquidStatusEffects) table["liquidStatusEffects"] = luaFunction(::liquidStatusEffects)
table["generateName"] = luaStub("generateName") table["generateName"] = luaFunction { asset: ByteString, seed: Number? ->
returnBuffer.setTo(Starbound.generateName(asset.decode(), if (seed == null) lua.random else random(seed.toLong())))
}
table["questConfig"] = registryDef(Registries.questTemplates) table["questConfig"] = registryDef(Registries.questTemplates)
table["npcPortrait"] = luaStub("npcPortrait") table["npcPortrait"] = luaStub("npcPortrait")