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
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.TypeAdapter
import org.apache.logging.log4j.LogManager
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.SystemWorldObjectConfig
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.mapAdapter
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.util.concurrent.CompletableFuture
@ -107,6 +110,32 @@ object Globals {
var itemParameters by Delegates.notNull<ItemGlobalConfig>()
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<*> {
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("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }))
tasks.add(load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }))
return tasks
}
}

View File

@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager
import org.lwjgl.Version
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
import ru.dbotthepony.kstarbound.util.random.random
import java.io.File
import java.net.InetSocketAddress
@ -27,5 +28,5 @@ fun main() {
Starbound.initializeGame().thenApply {
val server = IntegratedStarboundServer(client, File("./"))
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.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.JsonFunction
import ru.dbotthepony.kstarbound.defs.MarkovTextGenerator
import ru.dbotthepony.kstarbound.defs.Species
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
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 bushVariants = Registry<BushVariant.Data>("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
val dungeons = Registry<DungeonDefinition>("dungeon").also(registriesInternal::add).also { adapters.add(it.adapter()) }
val markovGenerators = Registry<MarkovTextGenerator>("markov text generator").also(registriesInternal::add).also { adapters.add(it.adapter()) }
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, KOptional<Int?>> {
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(treeFoliageVariants, patchTree, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::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(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.get
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.image.Image
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.animation.Particle
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.HashTableInterner
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 java.io.*
import java.lang.ref.Cleaner
import java.text.DateFormat
import java.time.Duration
import java.util.Collections
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
@ -87,6 +88,7 @@ import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.LockSupport
import java.util.random.RandomGenerator
import kotlin.NoSuchElementException
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.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 jParameters = toJsonFromLua(parameters).asJsonObject

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
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.lua.LUA_HINT_ARRAY
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.nextOptionalInteger
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toLuaInteger
import ru.dbotthepony.kstarbound.util.random.random
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.isNotEmpty
@ -71,7 +74,9 @@ private fun evalFunction2(context: ExecutionContext, name: ByteString, value: Do
}
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 {
@ -373,7 +378,10 @@ fun provideRootBindings(lua: LuaEnvironment) {
table["getMatchingTenants"] = luaFunction(::getMatchingTenants)
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["npcPortrait"] = luaStub("npcPortrait")