Ported viewer model, body and section_id URL parameters.

This commit is contained in:
Daan Vanden Bosch 2021-03-25 16:55:31 +01:00
parent 445878fb6e
commit 078cf7fb2e
6 changed files with 136 additions and 18 deletions

View File

@ -37,7 +37,7 @@ private fun download(
difficulty: Difficulty,
difficultyUrl: String,
enemyDrops: MutableList<EnemyDrop>,
boxDrops: MutableList<BoxDrop>,
@Suppress("UNUSED_PARAMETER") boxDrops: MutableList<BoxDrop>,
) {
val doc = Jsoup.connect("https://ephinea.pioneer2.net/drop-charts/${difficultyUrl}/").get()

View File

@ -49,7 +49,7 @@ class Application(
// The various tools Phantasmal World consists of.
val tools: List<PwTool> = listOf(
addDisposable(Viewer(assetLoader, createThreeRenderer)),
addDisposable(Viewer(assetLoader, uiStore, createThreeRenderer)),
addDisposable(QuestEditor(assetLoader, uiStore, createThreeRenderer)),
addDisposable(HuntOptimizer(assetLoader, uiStore)),
)

View File

@ -4,6 +4,7 @@ import kotlinx.browser.window
import kotlinx.coroutines.launch
import org.w3c.dom.events.KeyboardEvent
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.Disposer
import world.phantasmal.core.disposable.disposable
import world.phantasmal.observable.value.MutableVal
import world.phantasmal.observable.value.Val
@ -32,7 +33,8 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
* Maps full paths to maps of parameters and their values. In other words we keep track of
* parameter values per [applicationUrl].
*/
private val parameters: MutableMap<String, Map<String, String>> = mutableMapOf()
private val parameters: MutableMap<String, MutableMap<String, MutableVal<String?>>> =
mutableMapOf()
private val globalKeyDownHandlers: MutableMap<String, suspend (e: KeyboardEvent) -> Unit> =
mutableMapOf()
@ -43,7 +45,7 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
*/
private val features: MutableSet<String> = mutableSetOf()
val tools: List<PwToolType> = PwToolType.values().toList()
private val tools: List<PwToolType> = PwToolType.values().toList()
/**
* The default tool that is loaded.
@ -104,6 +106,53 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
}
}
fun registerParameter(
tool: PwToolType,
path: String,
parameter: String,
setInitialValue: (String?) -> Unit,
value: Val<String?>,
onChange: (String?) -> Unit,
): Disposable {
require(parameter !== FEATURES_PARAM) {
"$FEATURES_PARAM can't be set because it is a global parameter."
}
val pathParams = parameters.getOrPut("/${tool.slug}$path", ::mutableMapOf)
val param = pathParams.getOrPut(parameter) { mutableVal(null) }
setInitialValue(param.value)
value.value.let { v ->
if (v != param.value) {
setParameter(tool, path, param, v, replaceUrl = true)
}
}
return Disposer(
value.observe {
if (it.value != param.value) {
setParameter(tool, path, param, it.value, replaceUrl = false)
}
},
param.observe { onChange(it.value) },
)
}
private fun setParameter(
tool: PwToolType,
path: String,
parameter: MutableVal<String?>,
value: String?,
replaceUrl: Boolean,
) {
parameter.value = value
if (this.currentTool.value == tool && this.path.value == path) {
updateApplicationUrl(tool, path, replaceUrl)
}
}
fun onGlobalKeyDown(
tool: PwToolType,
binding: String,
@ -135,21 +184,19 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
val path = if (secondSlashIdx == -1) "" else fullPath.substring(secondSlashIdx)
if (paramsStr != null) {
val params = mutableMapOf<String, String>()
val params = parameters.getOrPut(fullPath, ::mutableMapOf)
for (p in paramsStr.split("&")) {
val (param, value) = p.split("=", limit = 2)
if (param == "features") {
if (param == FEATURES_PARAM) {
for (feature in value.split(",")) {
features.add(feature)
}
} else {
params[param] = value
params.getOrPut(param) { mutableVal(value) }.value = value
}
}
parameters[fullPath] = params
}
val actualTool = tool ?: defaultTool
@ -167,11 +214,14 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
private fun updateApplicationUrl(tool: PwToolType, path: String, replace: Boolean) {
val fullPath = "/${tool.slug}${path}"
val params: MutableMap<String, String> =
parameters[fullPath]?.let { HashMap(it) } ?: mutableMapOf()
val params = mutableMapOf<String, String>()
parameters[fullPath]?.forEach { (k, v) ->
v.value?.let { params[k] = it }
}
if (features.isNotEmpty()) {
params["features"] = features.joinToString(",")
params[FEATURES_PARAM] = features.joinToString(",")
}
val paramStr =
@ -210,6 +260,7 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
}
companion object {
private const val FEATURES_PARAM = "features"
private val SLUG_TO_PW_TOOL: Map<String, PwToolType> =
PwToolType.values().map { it.slug to it }.toMap()
}

View File

@ -5,6 +5,7 @@ import world.phantasmal.web.core.PwTool
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.loading.AssetLoader
import world.phantasmal.web.core.rendering.DisposableThreeRenderer
import world.phantasmal.web.core.stores.UiStore
import world.phantasmal.web.core.widgets.RendererWidget
import world.phantasmal.web.viewer.controllers.CharacterClassOptionsController
import world.phantasmal.web.viewer.controllers.ViewerController
@ -21,6 +22,7 @@ import world.phantasmal.webui.widgets.Widget
class Viewer(
private val assetLoader: AssetLoader,
private val uiStore: UiStore,
private val createThreeRenderer: (HTMLCanvasElement) -> DisposableThreeRenderer,
) : DisposableContainer(), PwTool {
override val toolType = PwToolType.Viewer
@ -30,7 +32,7 @@ class Viewer(
val characterClassAssetLoader = addDisposable(CharacterClassAssetLoader(assetLoader))
// Stores
val viewerStore = addDisposable(ViewerStore(characterClassAssetLoader))
val viewerStore = addDisposable(ViewerStore(characterClassAssetLoader, uiStore))
// Controllers
val viewerController = addDisposable(ViewerController(viewerStore))

View File

@ -11,6 +11,8 @@ import world.phantasmal.observable.value.Val
import world.phantasmal.observable.value.list.ListVal
import world.phantasmal.observable.value.list.mutableListVal
import world.phantasmal.observable.value.mutableVal
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.stores.UiStore
import world.phantasmal.web.shared.dto.SectionId
import world.phantasmal.web.viewer.loading.CharacterClassAssetLoader
import world.phantasmal.web.viewer.models.CharacterClass
@ -18,13 +20,15 @@ import world.phantasmal.webui.stores.Store
private val logger = KotlinLogging.logger {}
class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store() {
class ViewerStore(
private val assetLoader: CharacterClassAssetLoader,
uiStore: UiStore,
) : Store() {
private val _currentNinjaObject = mutableVal<NinjaObject<*>?>(null)
private val _currentTextures = mutableListVal<XvrTexture?>()
private val _currentCharacterClass = mutableVal<CharacterClass?>(CharacterClass.VALUES.random())
private val _currentCharacterClass = mutableVal<CharacterClass?>(null)
private val _currentSectionId = mutableVal(SectionId.VALUES.random())
private val _currentBody =
mutableVal((0 until _currentCharacterClass.value!!.bodyStyleCount).random())
private val _currentBody = mutableVal(0)
private val _currentNinjaMotion = mutableVal<NjMotion?>(null)
// Settings
@ -41,6 +45,61 @@ class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store()
val showSkeleton: Val<Boolean> = _showSkeleton
init {
addDisposables(
uiStore.registerParameter(
PwToolType.Viewer,
path = "",
MODEL_PARAM,
setInitialValue = { initialValue ->
_currentCharacterClass.value =
CharacterClass.VALUES.find { it.slug == initialValue }
?: CharacterClass.VALUES.random()
},
value = currentCharacterClass.map { it?.slug },
onChange = { newValue ->
scope.launch {
setCurrentCharacterClass(CharacterClass.VALUES.find { it.slug == newValue })
}
},
),
uiStore.registerParameter(
PwToolType.Viewer,
path = "",
BODY_PARAM,
setInitialValue = { initialValue ->
val maxBody = _currentCharacterClass.value?.bodyStyleCount?.minus(1) ?: 0
_currentBody.value =
initialValue?.toIntOrNull()?.takeIf { it <= maxBody }?.minus(1)
?: (0..maxBody).random()
},
value = currentBody.map { (it + 1).toString() },
onChange = { newValue ->
scope.launch {
setCurrentBody(newValue?.toIntOrNull()?.minus(1) ?: 0)
}
},
),
uiStore.registerParameter(
PwToolType.Viewer,
path = "",
SECTION_ID_PARAM,
setInitialValue = { initialValue ->
_currentSectionId.value =
initialValue?.let(SectionId::valueOf) ?: SectionId.VALUES.random()
},
value = currentSectionId.map { it.name },
onChange = { newValue ->
scope.launch {
setCurrentSectionId(
newValue?.let(SectionId::valueOf) ?: SectionId.VALUES.random()
)
}
},
),
)
scope.launch(Dispatchers.Default) {
loadCharacterClassNinjaObject(clearAnimation = true)
}
@ -119,4 +178,10 @@ class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store()
}
}
}
companion object {
private const val MODEL_PARAM = "model"
private const val BODY_PARAM = "body"
private const val SECTION_ID_PARAM = "section_id"
}
}

View File

@ -11,7 +11,7 @@ class ViewerTests : WebTestSuite {
components.applicationUrl = TestApplicationUrl("/${PwToolType.Viewer}")
val viewer = disposer.add(
Viewer(components.assetLoader, components.createThreeRenderer)
Viewer(components.assetLoader, components.uiStore, components.createThreeRenderer)
)
disposer.add(viewer.initialize())
}