Added character class options to the viewer again.

This commit is contained in:
Daan Vanden Bosch 2020-12-12 13:39:04 +01:00
parent 7433216982
commit 0f7189ab33
11 changed files with 160 additions and 6 deletions

View File

@ -39,6 +39,9 @@ infix fun Val<Boolean>.xor(other: Val<Boolean>): Val<Boolean> =
operator fun Val<Boolean>.not(): Val<Boolean> = map { !it }
operator fun Val<Int>.plus(other: Int): Val<Int> =
map { it + other }
fun Val<String>.isEmpty(): Val<Boolean> =
map { it.isEmpty() }

View File

@ -10,5 +10,12 @@ enum class SectionId {
Redria,
Oran,
Yellowboze,
Whitill,
Whitill;
val uiName: String = name
companion object {
val VALUES: Array<SectionId> = values()
val VALUES_LIST: List<SectionId> = VALUES.toList()
}
}

View File

@ -36,6 +36,7 @@ class RendererWidget(
width: 100%;
height: 100%;
outline: none;
background-color: #181818;
}
""".trimIndent())
}

View File

@ -6,12 +6,14 @@ 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.widgets.RendererWidget
import world.phantasmal.web.viewer.controller.CharacterClassOptionsController
import world.phantasmal.web.viewer.controller.ViewerController
import world.phantasmal.web.viewer.controller.ViewerToolbarController
import world.phantasmal.web.viewer.loading.CharacterClassAssetLoader
import world.phantasmal.web.viewer.rendering.MeshRenderer
import world.phantasmal.web.viewer.rendering.TextureRenderer
import world.phantasmal.web.viewer.store.ViewerStore
import world.phantasmal.web.viewer.widgets.CharacterClassOptionsWidget
import world.phantasmal.web.viewer.widgets.ViewerToolbar
import world.phantasmal.web.viewer.widgets.ViewerWidget
import world.phantasmal.webui.DisposableContainer
@ -33,6 +35,8 @@ class Viewer(
// Controllers
val viewerController = addDisposable(ViewerController(viewerStore))
val viewerToolbarController = addDisposable(ViewerToolbarController(viewerStore))
val characterClassOptionsController =
addDisposable(CharacterClassOptionsController(viewerStore))
// Rendering
val meshRenderer = addDisposable(
@ -46,6 +50,7 @@ class Viewer(
return ViewerWidget(
viewerController,
{ ViewerToolbar(viewerToolbarController) },
{ CharacterClassOptionsWidget(characterClassOptionsController) },
{ RendererWidget(meshRenderer) },
{ RendererWidget(textureRenderer) },
)

View File

@ -0,0 +1,24 @@
package world.phantasmal.web.viewer.controller
import world.phantasmal.observable.value.Val
import world.phantasmal.observable.value.plus
import world.phantasmal.web.core.models.SectionId
import world.phantasmal.web.viewer.store.ViewerStore
import world.phantasmal.webui.controllers.Controller
class CharacterClassOptionsController(private val store: ViewerStore) : Controller() {
val enabled = store.currentCharacterClass.isNotNull()
val currentSectionId: Val<SectionId> = store.currentSectionId
val currentBodyOptions: Val<List<Int>> = store.currentCharacterClass.map { char ->
if (char == null) emptyList() else (1..char.bodyStyleCount).toList()
}
val currentBody: Val<Int> = store.currentBody + 1
suspend fun setCurrentSectionId(sectionId: SectionId) {
store.setCurrentSectionId(sectionId)
}
suspend fun setCurrentBody(body: Int) {
store.setCurrentBody(body - 1)
}
}

View File

@ -14,7 +14,7 @@ sealed class ViewerTab(override val title: String) : Tab {
class ViewerController(private val store: ViewerStore) : TabContainerController<ViewerTab>(
tabs = listOf(ViewerTab.Mesh, ViewerTab.Texture)
) {
val characterClasses: List<CharacterClass> = CharacterClass.VALUES
val characterClasses: List<CharacterClass> = CharacterClass.VALUES_LIST
val currentCharacterClass: Val<CharacterClass?> = store.currentCharacterClass
suspend fun setCurrentCharacterClass(char: CharacterClass?) {

View File

@ -95,6 +95,7 @@ enum class CharacterClass(
val slug: String = name
companion object {
val VALUES: List<CharacterClass> = values().toList()
val VALUES: Array<CharacterClass> = values()
val VALUES_LIST: List<CharacterClass> = VALUES.toList()
}
}

View File

@ -1,5 +1,6 @@
package world.phantasmal.web.viewer.store
import kotlinx.coroutines.launch
import mu.KotlinLogging
import world.phantasmal.lib.fileFormats.ninja.NinjaObject
import world.phantasmal.lib.fileFormats.ninja.XvrTexture
@ -17,11 +18,22 @@ private val logger = KotlinLogging.logger {}
class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store() {
private val _currentNinjaObject = mutableVal<NinjaObject<*>?>(null)
private val _currentTextures = mutableListVal<XvrTexture?>(mutableListOf())
private val _currentCharacterClass = mutableVal<CharacterClass?>(null)
private val _currentCharacterClass = mutableVal<CharacterClass?>(CharacterClass.VALUES.random())
private val _currentSectionId = mutableVal(SectionId.VALUES.random())
private val _currentBody =
mutableVal((0 until _currentCharacterClass.value!!.bodyStyleCount).random())
val currentNinjaObject: Val<NinjaObject<*>?> = _currentNinjaObject
val currentTextures: ListVal<XvrTexture?> = _currentTextures
val currentCharacterClass: Val<CharacterClass?> = _currentCharacterClass
val currentSectionId: Val<SectionId> = _currentSectionId
val currentBody: Val<Int> = _currentBody
init {
scope.launch {
loadCharacterClassNinjaObject()
}
}
fun setCurrentNinjaObject(ninjaObject: NinjaObject<*>?) {
if (_currentCharacterClass.value != null) {
@ -38,6 +50,21 @@ class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store()
suspend fun setCurrentCharacterClass(char: CharacterClass?) {
_currentCharacterClass.value = char
if (char != null && _currentBody.value >= char.bodyStyleCount) {
_currentBody.value = char.bodyStyleCount - 1
}
loadCharacterClassNinjaObject()
}
suspend fun setCurrentSectionId(sectionId: SectionId) {
_currentSectionId.value = sectionId
loadCharacterClassNinjaObject()
}
suspend fun setCurrentBody(body: Int) {
_currentBody.value = body
loadCharacterClassNinjaObject()
}
@ -45,9 +72,12 @@ class ViewerStore(private val assetLoader: CharacterClassAssetLoader) : Store()
val char = currentCharacterClass.value
?: return
val sectionId = currentSectionId.value
val body = currentBody.value
try {
val ninjaObject = assetLoader.loadNinjaObject(char)
val textures = assetLoader.loadXvrTextures(char, SectionId.Whitill, 0)
val textures = assetLoader.loadXvrTextures(char, sectionId, body)
_currentNinjaObject.value = ninjaObject
_currentTextures.replaceAll(textures)
} catch (e: Exception) {

View File

@ -0,0 +1,75 @@
package world.phantasmal.web.viewer.widgets
import kotlinx.coroutines.launch
import org.w3c.dom.Node
import world.phantasmal.observable.value.value
import world.phantasmal.web.core.models.SectionId
import world.phantasmal.web.viewer.controller.CharacterClassOptionsController
import world.phantasmal.webui.dom.div
import world.phantasmal.webui.dom.table
import world.phantasmal.webui.dom.td
import world.phantasmal.webui.dom.tr
import world.phantasmal.webui.widgets.Select
import world.phantasmal.webui.widgets.Widget
class CharacterClassOptionsWidget(private val ctrl: CharacterClassOptionsController) : Widget() {
override fun Node.createElement() =
div {
className = "pw-viewer-character-class-options"
table {
tr {
val sectionIdSelect = Select(
enabled = ctrl.enabled,
className = "pw-viewer-character-class-options-section-id",
label = "Section ID:",
items = value(SectionId.VALUES_LIST),
selected = ctrl.currentSectionId,
onSelect = { sectionId ->
scope.launch { ctrl.setCurrentSectionId(sectionId) }
},
itemToString = { it.uiName },
)
td { addChild(sectionIdSelect.label!!) }
td { addChild(sectionIdSelect) }
}
tr {
val bodySelect = Select(
enabled = ctrl.enabled,
className = "pw-viewer-character-class-options-body",
label = "Body:",
items = ctrl.currentBodyOptions,
selected = ctrl.currentBody,
onSelect = { body ->
scope.launch { ctrl.setCurrentBody(body) }
},
)
td { addChild(bodySelect.label!!) }
td { addChild(bodySelect) }
}
}
}
companion object {
init {
@Suppress("CssUnresolvedCustomProperty", "CssUnusedSymbol")
// language=css
style("""
.pw-viewer-character-class-options {
box-sizing: border-box;
border-left: var(--pw-border);
border-right: var(--pw-border);
padding: 0 0 0 4px;
}
.pw-viewer-character-class-options-section-id {
width: 120px;
}
.pw-viewer-character-class-options-body {
width: 60px;
}
""".trimIndent())
}
}
}

View File

@ -11,6 +11,7 @@ import world.phantasmal.webui.widgets.Widget
class ViewerWidget(
private val ctrl: ViewerController,
private val createToolbar: () -> Widget,
private val createCharacterClassOptionsWidget: () -> CharacterClassOptionsWidget,
private val createMeshWidget: () -> Widget,
private val createTextureWidget: () -> Widget,
) : Widget() {
@ -29,6 +30,7 @@ class ViewerWidget(
{ char -> scope.launch { ctrl.setCurrentCharacterClass(char) } },
{ it.uiName },
))
addChild(createCharacterClassOptionsWidget())
addChild(TabContainer(ctrl = ctrl, createWidget = { tab ->
when (tab) {
ViewerTab.Mesh -> createMeshWidget()

View File

@ -3,8 +3,11 @@ package world.phantasmal.webui.widgets
import org.w3c.dom.Node
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.MouseEvent
import world.phantasmal.observable.value.*
import world.phantasmal.observable.value.Val
import world.phantasmal.observable.value.list.emptyListVal
import world.phantasmal.observable.value.mutableVal
import world.phantasmal.observable.value.nullVal
import world.phantasmal.observable.value.trueVal
import world.phantasmal.webui.dom.Icon
import world.phantasmal.webui.dom.div
@ -12,6 +15,7 @@ class Select<T : Any>(
visible: Val<Boolean> = trueVal(),
enabled: Val<Boolean> = trueVal(),
tooltip: Val<String?> = nullVal(),
private val className: String? = null,
label: String? = null,
labelVal: Val<String>? = null,
preferredLabelPosition: LabelPosition = LabelPosition.Before,
@ -40,6 +44,8 @@ class Select<T : Any>(
div {
className = "pw-select"
this@Select.className?.let { classList.add(it) }
// Default to a single space so the inner text part won't be hidden.
observe(selected) { buttonText.value = it?.let(itemToString) ?: " " }