mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Entities are now shown per area and area selection is now possible. Fixed some bugs.
This commit is contained in:
parent
db1149ddc0
commit
132cdccd0a
@ -70,9 +70,9 @@ class QuestNpc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var sectionId: Int
|
override var sectionId: Int
|
||||||
get() = data.getUShort(12).toInt()
|
get() = data.getShort(12).toInt()
|
||||||
set(value) {
|
set(value) {
|
||||||
data.setUShort(12, value.toUShort())
|
data.setShort(12, value.toShort())
|
||||||
}
|
}
|
||||||
|
|
||||||
override var position: Vec3
|
override var position: Vec3
|
||||||
|
@ -20,9 +20,9 @@ class QuestObject(override var areaId: Int, val data: Buffer) : QuestEntity<Obje
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var sectionId: Int
|
override var sectionId: Int
|
||||||
get() = data.getUShort(12).toInt()
|
get() = data.getShort(12).toInt()
|
||||||
set(value) {
|
set(value) {
|
||||||
data.setUShort(12, value.toUShort())
|
data.setShort(12, value.toShort())
|
||||||
}
|
}
|
||||||
|
|
||||||
override var position: Vec3
|
override var position: Vec3
|
||||||
|
@ -2,19 +2,20 @@ package world.phantasmal.observable.value
|
|||||||
|
|
||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.disposable
|
import world.phantasmal.core.disposable.disposable
|
||||||
|
import world.phantasmal.observable.ChangeEvent
|
||||||
import world.phantasmal.observable.Observer
|
import world.phantasmal.observable.Observer
|
||||||
|
|
||||||
abstract class AbstractVal<T> : Val<T> {
|
abstract class AbstractVal<T> : Val<T> {
|
||||||
protected val observers: MutableList<ValObserver<T>> = mutableListOf()
|
protected val observers: MutableList<Observer<T>> = mutableListOf()
|
||||||
|
|
||||||
final override fun observe(observer: Observer<T>): Disposable =
|
final override fun observe(observer: Observer<T>): Disposable =
|
||||||
observe(callNow = false, observer)
|
observe(callNow = false, observer)
|
||||||
|
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||||
observers.add(observer)
|
observers.add(observer)
|
||||||
|
|
||||||
if (callNow) {
|
if (callNow) {
|
||||||
observer(ValChangeEvent(value, value))
|
observer(ChangeEvent(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
return disposable {
|
return disposable {
|
||||||
@ -22,8 +23,8 @@ abstract class AbstractVal<T> : Val<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun emit(oldValue: T) {
|
protected fun emit() {
|
||||||
val event = ValChangeEvent(value, oldValue)
|
val event = ChangeEvent(value)
|
||||||
observers.forEach { it(event) }
|
observers.forEach { it(event) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class DelegatingVal<T>(
|
|||||||
|
|
||||||
if (value != oldValue) {
|
if (value != oldValue) {
|
||||||
setter(value)
|
setter(value)
|
||||||
emit(oldValue)
|
emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package world.phantasmal.observable.value
|
|||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.disposable
|
import world.phantasmal.core.disposable.disposable
|
||||||
import world.phantasmal.core.unsafeToNonNull
|
import world.phantasmal.core.unsafeToNonNull
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts observing its dependencies when the first observer on this val is registered. Stops
|
* Starts observing its dependencies when the first observer on this val is registered. Stops
|
||||||
@ -17,19 +18,26 @@ abstract class DependentVal<T>(
|
|||||||
*/
|
*/
|
||||||
private val dependencyObservers = mutableListOf<Disposable>()
|
private val dependencyObservers = mutableListOf<Disposable>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true right before actual observers are added.
|
||||||
|
*/
|
||||||
|
protected var hasObservers = false
|
||||||
|
|
||||||
protected var _value: T? = null
|
protected var _value: T? = null
|
||||||
|
|
||||||
override val value: T
|
override val value: T
|
||||||
get() {
|
get() {
|
||||||
if (hasNoObservers()) {
|
if (!hasObservers) {
|
||||||
_value = computeValue()
|
_value = computeValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return _value.unsafeToNonNull()
|
return _value.unsafeToNonNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||||
if (hasNoObservers()) {
|
if (dependencyObservers.isEmpty()) {
|
||||||
|
hasObservers = true
|
||||||
|
|
||||||
dependencies.forEach { dependency ->
|
dependencies.forEach { dependency ->
|
||||||
dependencyObservers.add(
|
dependencyObservers.add(
|
||||||
dependency.observe {
|
dependency.observe {
|
||||||
@ -37,7 +45,7 @@ abstract class DependentVal<T>(
|
|||||||
_value = computeValue()
|
_value = computeValue()
|
||||||
|
|
||||||
if (_value != oldValue) {
|
if (_value != oldValue) {
|
||||||
emit(oldValue.unsafeToNonNull())
|
emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -52,17 +60,12 @@ abstract class DependentVal<T>(
|
|||||||
superDisposable.dispose()
|
superDisposable.dispose()
|
||||||
|
|
||||||
if (observers.isEmpty()) {
|
if (observers.isEmpty()) {
|
||||||
|
hasObservers = false
|
||||||
dependencyObservers.forEach { it.dispose() }
|
dependencyObservers.forEach { it.dispose() }
|
||||||
dependencyObservers.clear()
|
dependencyObservers.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun hasObservers(): Boolean =
|
|
||||||
dependencyObservers.isNotEmpty()
|
|
||||||
|
|
||||||
protected fun hasNoObservers(): Boolean =
|
|
||||||
dependencyObservers.isEmpty()
|
|
||||||
|
|
||||||
protected abstract fun computeValue(): T
|
protected abstract fun computeValue(): T
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package world.phantasmal.observable.value
|
|||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.disposable
|
import world.phantasmal.core.disposable.disposable
|
||||||
import world.phantasmal.core.unsafeToNonNull
|
import world.phantasmal.core.unsafeToNonNull
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
|
|
||||||
class FlatMappedVal<T>(
|
class FlatMappedVal<T>(
|
||||||
dependencies: Iterable<Val<*>>,
|
dependencies: Iterable<Val<*>>,
|
||||||
@ -13,20 +14,20 @@ class FlatMappedVal<T>(
|
|||||||
|
|
||||||
override val value: T
|
override val value: T
|
||||||
get() {
|
get() {
|
||||||
return if (hasNoObservers()) {
|
return if (hasObservers) {
|
||||||
super.value
|
|
||||||
} else {
|
|
||||||
computedVal.unsafeToNonNull().value
|
computedVal.unsafeToNonNull().value
|
||||||
|
} else {
|
||||||
|
super.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||||
val superDisposable = super.observe(callNow, observer)
|
val superDisposable = super.observe(callNow, observer)
|
||||||
|
|
||||||
return disposable {
|
return disposable {
|
||||||
superDisposable.dispose()
|
superDisposable.dispose()
|
||||||
|
|
||||||
if (hasNoObservers()) {
|
if (!hasObservers) {
|
||||||
computedValObserver?.dispose()
|
computedValObserver?.dispose()
|
||||||
computedValObserver = null
|
computedValObserver = null
|
||||||
computedVal = null
|
computedVal = null
|
||||||
@ -40,11 +41,10 @@ class FlatMappedVal<T>(
|
|||||||
|
|
||||||
computedValObserver?.dispose()
|
computedValObserver?.dispose()
|
||||||
|
|
||||||
if (hasObservers()) {
|
if (hasObservers) {
|
||||||
computedValObserver = computedVal.observe { (value) ->
|
computedValObserver = computedVal.observe { (value) ->
|
||||||
val oldValue = _value.unsafeToNonNull<T>()
|
|
||||||
_value = value
|
_value = value
|
||||||
emit(oldValue)
|
emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,8 @@ class SimpleVal<T>(value: T) : AbstractVal<T>(), MutableVal<T> {
|
|||||||
override var value: T = value
|
override var value: T = value
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value != field) {
|
if (value != field) {
|
||||||
val oldValue = field
|
|
||||||
field = value
|
field = value
|
||||||
emit(oldValue)
|
emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@ package world.phantasmal.observable.value
|
|||||||
|
|
||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.stubDisposable
|
import world.phantasmal.core.disposable.stubDisposable
|
||||||
|
import world.phantasmal.observable.ChangeEvent
|
||||||
import world.phantasmal.observable.Observer
|
import world.phantasmal.observable.Observer
|
||||||
|
|
||||||
class StaticVal<T>(override val value: T) : Val<T> {
|
class StaticVal<T>(override val value: T) : Val<T> {
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||||
if (callNow) {
|
if (callNow) {
|
||||||
observer(ValChangeEvent(value, value))
|
observer(ChangeEvent(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
return stubDisposable()
|
return stubDisposable()
|
||||||
|
@ -2,6 +2,7 @@ package world.phantasmal.observable.value
|
|||||||
|
|
||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.observable.Observable
|
import world.phantasmal.observable.Observable
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,7 +16,7 @@ interface Val<out T> : Observable<T> {
|
|||||||
/**
|
/**
|
||||||
* @param callNow Call [observer] immediately with the current [mutableVal].
|
* @param callNow Call [observer] immediately with the current [mutableVal].
|
||||||
*/
|
*/
|
||||||
fun observe(callNow: Boolean = false, observer: ValObserver<T>): Disposable
|
fun observe(callNow: Boolean = false, observer: Observer<T>): Disposable
|
||||||
|
|
||||||
fun <R> map(transform: (T) -> R): Val<R> =
|
fun <R> map(transform: (T) -> R): Val<R> =
|
||||||
MappedVal(listOf(this)) { transform(value) }
|
MappedVal(listOf(this)) { transform(value) }
|
||||||
@ -23,6 +24,9 @@ interface Val<out T> : Observable<T> {
|
|||||||
fun <T2, R> map(v2: Val<T2>, transform: (T, T2) -> R): Val<R> =
|
fun <T2, R> map(v2: Val<T2>, transform: (T, T2) -> R): Val<R> =
|
||||||
MappedVal(listOf(this, v2)) { transform(value, v2.value) }
|
MappedVal(listOf(this, v2)) { transform(value, v2.value) }
|
||||||
|
|
||||||
|
fun <T2, T3, R> map(v2: Val<T2>, v3: Val<T3>, transform: (T, T2, T3) -> R): Val<R> =
|
||||||
|
MappedVal(listOf(this, v2, v3)) { transform(value, v2.value, v3.value) }
|
||||||
|
|
||||||
fun <R> flatMap(transform: (T) -> Val<R>): Val<R> =
|
fun <R> flatMap(transform: (T) -> Val<R>): Val<R> =
|
||||||
FlatMappedVal(listOf(this)) { transform(value) }
|
FlatMappedVal(listOf(this)) { transform(value) }
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package world.phantasmal.observable.value
|
|
||||||
|
|
||||||
import world.phantasmal.observable.ChangeEvent
|
|
||||||
|
|
||||||
class ValChangeEvent<out T>(value: T, val oldValue: T) : ChangeEvent<T>(value) {
|
|
||||||
operator fun component2() = oldValue
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias ValObserver<T> = (event: ValChangeEvent<T>) -> Unit
|
|
@ -0,0 +1,135 @@
|
|||||||
|
package world.phantasmal.observable.value.list
|
||||||
|
|
||||||
|
import world.phantasmal.core.disposable.Disposable
|
||||||
|
import world.phantasmal.core.disposable.disposable
|
||||||
|
import world.phantasmal.observable.ChangeEvent
|
||||||
|
import world.phantasmal.observable.Observable
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
|
import world.phantasmal.observable.value.AbstractVal
|
||||||
|
import world.phantasmal.observable.value.MutableVal
|
||||||
|
import world.phantasmal.observable.value.Val
|
||||||
|
import world.phantasmal.observable.value.mutableVal
|
||||||
|
|
||||||
|
abstract class AbstractListVal<E>(
|
||||||
|
protected val elements: MutableList<E>,
|
||||||
|
private val extractObservables: ObservablesExtractor<E>? ,
|
||||||
|
): AbstractVal<List<E>>(), ListVal<E> {
|
||||||
|
/**
|
||||||
|
* Internal observers which observe observables related to this list's elements so that their
|
||||||
|
* changes can be propagated via ElementChange events.
|
||||||
|
*/
|
||||||
|
private val elementObservers = mutableListOf<ElementObserver>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External list observers which are observing this list.
|
||||||
|
*/
|
||||||
|
protected val listObservers = mutableListOf<ListValObserver<E>>()
|
||||||
|
|
||||||
|
override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable {
|
||||||
|
if (elementObservers.isEmpty() && extractObservables != null) {
|
||||||
|
replaceElementObservers(0, elementObservers.size, elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
observers.add(observer)
|
||||||
|
|
||||||
|
if (callNow) {
|
||||||
|
observer(ChangeEvent(elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
observers.remove(observer)
|
||||||
|
disposeElementObserversIfNecessary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observeList(callNow: Boolean, observer: ListValObserver<E>): Disposable {
|
||||||
|
if (elementObservers.isEmpty() && extractObservables != null) {
|
||||||
|
replaceElementObservers(0, elementObservers.size, elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
listObservers.add(observer)
|
||||||
|
|
||||||
|
if (callNow) {
|
||||||
|
observer(ListValChangeEvent.Change(0, emptyList(), elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
listObservers.remove(observer)
|
||||||
|
disposeElementObserversIfNecessary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the following in the given order:
|
||||||
|
* - Updates element observers
|
||||||
|
* - Emits ListValChangeEvent
|
||||||
|
* - Emits ValChangeEvent
|
||||||
|
*/
|
||||||
|
protected open fun finalizeUpdate(event: ListValChangeEvent<E>) {
|
||||||
|
if (
|
||||||
|
(listObservers.isNotEmpty() || observers.isNotEmpty()) &&
|
||||||
|
extractObservables != null &&
|
||||||
|
event is ListValChangeEvent.Change
|
||||||
|
) {
|
||||||
|
replaceElementObservers(event.index, event.removed.size, event.inserted)
|
||||||
|
}
|
||||||
|
|
||||||
|
listObservers.forEach { observer: ListValObserver<E> ->
|
||||||
|
observer(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceElementObservers(from: Int, amountRemoved: Int, insertedElements: List<E>) {
|
||||||
|
for (i in 1..amountRemoved) {
|
||||||
|
elementObservers.removeAt(from).observers.forEach { it.dispose() }
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = from
|
||||||
|
|
||||||
|
elementObservers.addAll(
|
||||||
|
from,
|
||||||
|
insertedElements.map { element ->
|
||||||
|
ElementObserver(
|
||||||
|
index++,
|
||||||
|
element,
|
||||||
|
extractObservables!!(element)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val shift = insertedElements.size - amountRemoved
|
||||||
|
|
||||||
|
while (index < elementObservers.size) {
|
||||||
|
elementObservers[index++].index += shift
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disposeElementObserversIfNecessary() {
|
||||||
|
if (listObservers.isEmpty() && observers.isEmpty()) {
|
||||||
|
elementObservers.forEach { elementObserver: ElementObserver ->
|
||||||
|
elementObserver.observers.forEach { it.dispose() }
|
||||||
|
}
|
||||||
|
|
||||||
|
elementObservers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ElementObserver(
|
||||||
|
var index: Int,
|
||||||
|
element: E,
|
||||||
|
observables: Array<Observable<*>>,
|
||||||
|
) {
|
||||||
|
val observers: Array<Disposable> = Array(observables.size) {
|
||||||
|
observables[it].observe {
|
||||||
|
finalizeUpdate(
|
||||||
|
ListValChangeEvent.ElementChange(
|
||||||
|
index,
|
||||||
|
listOf(element)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package world.phantasmal.observable.value.list
|
||||||
|
|
||||||
|
import world.phantasmal.core.disposable.Disposable
|
||||||
|
import world.phantasmal.core.disposable.disposable
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
|
import world.phantasmal.observable.value.AbstractVal
|
||||||
|
import world.phantasmal.observable.value.Val
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts observing its dependencies when the first observer on this property is registered.
|
||||||
|
* Stops observing its dependencies when the last observer on this property is disposed.
|
||||||
|
* This way no extra disposables need to be managed when e.g. [map] is used.
|
||||||
|
*/
|
||||||
|
class DependentListVal<E>(
|
||||||
|
private val dependencies: List<Val<*>>,
|
||||||
|
private val computeElements: () -> List<E>,
|
||||||
|
) : AbstractListVal<E>(mutableListOf(), extractObservables = null) {
|
||||||
|
private val _sizeVal = SizeVal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true right before actual observers are added.
|
||||||
|
*/
|
||||||
|
private var hasObservers = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is either empty or has a disposable per dependency.
|
||||||
|
*/
|
||||||
|
private val dependencyObservers = mutableListOf<Disposable>()
|
||||||
|
|
||||||
|
override val value: List<E>
|
||||||
|
get() {
|
||||||
|
if (!hasObservers) {
|
||||||
|
recompute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
|
|
||||||
|
override val sizeVal: Val<Int> = _sizeVal
|
||||||
|
|
||||||
|
override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable {
|
||||||
|
initDependencyObservers()
|
||||||
|
|
||||||
|
val superDisposable = super.observe(callNow, observer)
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
superDisposable.dispose()
|
||||||
|
disposeDependencyObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observeList(callNow: Boolean, observer: ListValObserver<E>): Disposable {
|
||||||
|
initDependencyObservers()
|
||||||
|
|
||||||
|
val superDisposable = super.observeList(callNow, observer)
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
superDisposable.dispose()
|
||||||
|
disposeDependencyObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recompute() {
|
||||||
|
elements.clear()
|
||||||
|
elements.addAll(computeElements())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDependencyObservers() {
|
||||||
|
if (dependencyObservers.isEmpty()) {
|
||||||
|
hasObservers = true
|
||||||
|
|
||||||
|
dependencies.forEach { dependency ->
|
||||||
|
dependencyObservers.add(
|
||||||
|
dependency.observe {
|
||||||
|
val removed = ArrayList(elements)
|
||||||
|
recompute()
|
||||||
|
finalizeUpdate(ListValChangeEvent.Change(0, removed, elements))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
recompute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disposeDependencyObservers() {
|
||||||
|
if (observers.isEmpty() && listObservers.isEmpty() && _sizeVal.publicObservers.isEmpty()) {
|
||||||
|
hasObservers = false
|
||||||
|
dependencyObservers.forEach { it.dispose() }
|
||||||
|
dependencyObservers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finalizeUpdate(event: ListValChangeEvent<E>) {
|
||||||
|
if (event is ListValChangeEvent.Change && event.removed.size != event.inserted.size) {
|
||||||
|
_sizeVal.publicEmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.finalizeUpdate(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class SizeVal : AbstractVal<Int>() {
|
||||||
|
override val value: Int
|
||||||
|
get() {
|
||||||
|
if (!hasObservers) {
|
||||||
|
recompute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements.size
|
||||||
|
}
|
||||||
|
|
||||||
|
val publicObservers = super.observers
|
||||||
|
|
||||||
|
override fun observe(callNow: Boolean, observer: Observer<Int>): Disposable {
|
||||||
|
initDependencyObservers()
|
||||||
|
|
||||||
|
val superDisposable = super.observe(callNow, observer)
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
superDisposable.dispose()
|
||||||
|
disposeDependencyObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicEmit() {
|
||||||
|
super.emit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,8 @@ package world.phantasmal.observable.value.list
|
|||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.disposable
|
import world.phantasmal.core.disposable.disposable
|
||||||
import world.phantasmal.core.unsafeToNonNull
|
import world.phantasmal.core.unsafeToNonNull
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
import world.phantasmal.observable.value.AbstractVal
|
import world.phantasmal.observable.value.AbstractVal
|
||||||
import world.phantasmal.observable.value.ValObserver
|
|
||||||
|
|
||||||
class FoldedVal<T, R>(
|
class FoldedVal<T, R>(
|
||||||
private val dependency: ListVal<T>,
|
private val dependency: ListVal<T>,
|
||||||
@ -23,16 +23,15 @@ class FoldedVal<T, R>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<R>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<R>): Disposable {
|
||||||
val superDisposable = super.observe(callNow, observer)
|
val superDisposable = super.observe(callNow, observer)
|
||||||
|
|
||||||
if (dependencyDisposable == null) {
|
if (dependencyDisposable == null) {
|
||||||
internalValue = computeValue()
|
internalValue = computeValue()
|
||||||
|
|
||||||
dependencyDisposable = dependency.observe {
|
dependencyDisposable = dependency.observe {
|
||||||
val oldValue = internalValue
|
|
||||||
internalValue = computeValue()
|
internalValue = computeValue()
|
||||||
emit(oldValue.unsafeToNonNull())
|
emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,4 +13,7 @@ interface ListVal<E> : Val<List<E>> {
|
|||||||
|
|
||||||
fun <R> fold(initialValue: R, operation: (R, E) -> R): Val<R> =
|
fun <R> fold(initialValue: R, operation: (R, E) -> R): Val<R> =
|
||||||
FoldedVal(this, initialValue, operation)
|
FoldedVal(this, initialValue, operation)
|
||||||
|
|
||||||
|
fun filtered(predicate: (E) -> Boolean): ListVal<E> =
|
||||||
|
DependentListVal(listOf(this)) { value.filter(predicate) }
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,29 @@
|
|||||||
package world.phantasmal.observable.value.list
|
package world.phantasmal.observable.value.list
|
||||||
|
|
||||||
import world.phantasmal.core.disposable.Disposable
|
|
||||||
import world.phantasmal.core.disposable.disposable
|
|
||||||
import world.phantasmal.observable.Observable
|
import world.phantasmal.observable.Observable
|
||||||
import world.phantasmal.observable.Observer
|
import world.phantasmal.observable.value.MutableVal
|
||||||
import world.phantasmal.observable.value.*
|
import world.phantasmal.observable.value.Val
|
||||||
|
import world.phantasmal.observable.value.mutableVal
|
||||||
|
|
||||||
typealias ObservablesExtractor<E> = (element: E) -> Array<Observable<*>>
|
typealias ObservablesExtractor<E> = (element: E) -> Array<Observable<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param elements The backing list for this ListVal
|
||||||
|
* @param extractObservables Extractor function called on each element in this list, changes to the
|
||||||
|
* returned observables will be propagated via ElementChange events
|
||||||
|
*/
|
||||||
class SimpleListVal<E>(
|
class SimpleListVal<E>(
|
||||||
private val elements: MutableList<E>,
|
elements: MutableList<E>,
|
||||||
/**
|
extractObservables: ObservablesExtractor<E>? = null,
|
||||||
* Extractor function called on each element in this list. Changes to the returned observables
|
) : AbstractListVal<E>(elements, extractObservables), MutableListVal<E> {
|
||||||
* will be propagated via ElementChange events.
|
private val _sizeVal: MutableVal<Int> = mutableVal(elements.size)
|
||||||
*/
|
|
||||||
private val extractObservables: ObservablesExtractor<E>? = null,
|
|
||||||
) : MutableListVal<E> {
|
|
||||||
override var value: List<E> = elements
|
override var value: List<E> = elements
|
||||||
set(value) {
|
set(value) {
|
||||||
val removed = ArrayList(elements)
|
replaceAll(value)
|
||||||
elements.clear()
|
|
||||||
elements.addAll(value)
|
|
||||||
finalizeUpdate(
|
|
||||||
ListValChangeEvent.Change(
|
|
||||||
index = 0,
|
|
||||||
removed = removed,
|
|
||||||
inserted = value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mutableSizeVal: MutableVal<Int> = mutableVal(elements.size)
|
override val sizeVal: Val<Int> = _sizeVal
|
||||||
|
|
||||||
override val sizeVal: Val<Int> = mutableSizeVal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal observers which observe observables related to this list's elements so that their
|
|
||||||
* changes can be propagated via ElementChange events.
|
|
||||||
*/
|
|
||||||
private val elementObservers = mutableListOf<ElementObserver>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* External list observers which are observing this list.
|
|
||||||
*/
|
|
||||||
private val listObservers = mutableListOf<ListValObserver<E>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* External regular observers which are observing this list.
|
|
||||||
*/
|
|
||||||
private val observers = mutableListOf<ValObserver<List<E>>>()
|
|
||||||
|
|
||||||
override fun set(index: Int, element: E): E {
|
override fun set(index: Int, element: E): E {
|
||||||
val removed = elements.set(index, element)
|
val removed = elements.set(index, element)
|
||||||
@ -93,121 +68,8 @@ class SimpleListVal<E>(
|
|||||||
finalizeUpdate(ListValChangeEvent.Change(0, removed, emptyList()))
|
finalizeUpdate(ListValChangeEvent.Change(0, removed, emptyList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun observe(observer: Observer<List<E>>): Disposable =
|
override fun finalizeUpdate(event: ListValChangeEvent<E>) {
|
||||||
observe(callNow = false, observer)
|
_sizeVal.value = elements.size
|
||||||
|
super.finalizeUpdate(event)
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<List<E>>): Disposable {
|
|
||||||
if (elementObservers.isEmpty() && extractObservables != null) {
|
|
||||||
replaceElementObservers(0, elementObservers.size, elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
observers.add(observer)
|
|
||||||
|
|
||||||
if (callNow) {
|
|
||||||
observer(ValChangeEvent(elements, elements))
|
|
||||||
}
|
|
||||||
|
|
||||||
return disposable {
|
|
||||||
observers.remove(observer)
|
|
||||||
disposeElementObserversIfNecessary()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun observeList(callNow: Boolean, observer: ListValObserver<E>): Disposable {
|
|
||||||
if (elementObservers.isEmpty() && extractObservables != null) {
|
|
||||||
replaceElementObservers(0, elementObservers.size, elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
listObservers.add(observer)
|
|
||||||
|
|
||||||
if (callNow) {
|
|
||||||
observer(ListValChangeEvent.Change(0, emptyList(), elements))
|
|
||||||
}
|
|
||||||
|
|
||||||
return disposable {
|
|
||||||
listObservers.remove(observer)
|
|
||||||
disposeElementObserversIfNecessary()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the following in the given order:
|
|
||||||
* - Updates element observers
|
|
||||||
* - Emits size ValChangeEvent if necessary
|
|
||||||
* - Emits ListValChangeEvent
|
|
||||||
* - Emits ValChangeEvent
|
|
||||||
*/
|
|
||||||
private fun finalizeUpdate(event: ListValChangeEvent<E>) {
|
|
||||||
if (
|
|
||||||
(listObservers.isNotEmpty() || observers.isNotEmpty()) &&
|
|
||||||
extractObservables != null &&
|
|
||||||
event is ListValChangeEvent.Change
|
|
||||||
) {
|
|
||||||
replaceElementObservers(event.index, event.removed.size, event.inserted)
|
|
||||||
}
|
|
||||||
|
|
||||||
mutableSizeVal.value = elements.size
|
|
||||||
|
|
||||||
listObservers.forEach { observer: ListValObserver<E> ->
|
|
||||||
observer(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
val regularEvent = ValChangeEvent(elements, elements)
|
|
||||||
|
|
||||||
observers.forEach { observer: ValObserver<List<E>> ->
|
|
||||||
observer(regularEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceElementObservers(from: Int, amountRemoved: Int, insertedElements: List<E>) {
|
|
||||||
for (i in 1..amountRemoved) {
|
|
||||||
elementObservers.removeAt(from).observers.forEach { it.dispose() }
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = from
|
|
||||||
|
|
||||||
elementObservers.addAll(
|
|
||||||
from,
|
|
||||||
insertedElements.map { element ->
|
|
||||||
ElementObserver(
|
|
||||||
index++,
|
|
||||||
element,
|
|
||||||
extractObservables!!(element)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val shift = insertedElements.size - amountRemoved
|
|
||||||
|
|
||||||
while (index < elementObservers.size) {
|
|
||||||
elementObservers[index++].index += shift
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun disposeElementObserversIfNecessary() {
|
|
||||||
if (listObservers.isEmpty() && observers.isEmpty()) {
|
|
||||||
elementObservers.forEach { elementObserver: ElementObserver ->
|
|
||||||
elementObserver.observers.forEach { it.dispose() }
|
|
||||||
}
|
|
||||||
|
|
||||||
elementObservers.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class ElementObserver(
|
|
||||||
var index: Int,
|
|
||||||
element: E,
|
|
||||||
observables: Array<Observable<*>>,
|
|
||||||
) {
|
|
||||||
val observers: Array<Disposable> = Array(observables.size) {
|
|
||||||
observables[it].observe {
|
|
||||||
finalizeUpdate(
|
|
||||||
ListValChangeEvent.ElementChange(
|
|
||||||
index,
|
|
||||||
listOf(element)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,9 @@ package world.phantasmal.observable.value.list
|
|||||||
|
|
||||||
import world.phantasmal.core.disposable.Disposable
|
import world.phantasmal.core.disposable.Disposable
|
||||||
import world.phantasmal.core.disposable.stubDisposable
|
import world.phantasmal.core.disposable.stubDisposable
|
||||||
|
import world.phantasmal.observable.ChangeEvent
|
||||||
import world.phantasmal.observable.Observer
|
import world.phantasmal.observable.Observer
|
||||||
import world.phantasmal.observable.value.Val
|
import world.phantasmal.observable.value.Val
|
||||||
import world.phantasmal.observable.value.ValChangeEvent
|
|
||||||
import world.phantasmal.observable.value.ValObserver
|
|
||||||
import world.phantasmal.observable.value.value
|
import world.phantasmal.observable.value.value
|
||||||
|
|
||||||
class StaticListVal<E>(elements: List<E>) : ListVal<E> {
|
class StaticListVal<E>(elements: List<E>) : ListVal<E> {
|
||||||
@ -13,9 +12,9 @@ class StaticListVal<E>(elements: List<E>) : ListVal<E> {
|
|||||||
|
|
||||||
override val value: List<E> = elements
|
override val value: List<E> = elements
|
||||||
|
|
||||||
override fun observe(callNow: Boolean, observer: ValObserver<List<E>>): Disposable {
|
override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable {
|
||||||
if (callNow) {
|
if (callNow) {
|
||||||
observer(ValChangeEvent(value, value))
|
observer(ChangeEvent(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
return stubDisposable()
|
return stubDisposable()
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package world.phantasmal.observable.value.list
|
||||||
|
|
||||||
|
class DependentListValTests : ListValTests() {
|
||||||
|
override fun create(): ListValAndAdd {
|
||||||
|
val l = SimpleListVal<Int>(mutableListOf())
|
||||||
|
val list = DependentListVal(listOf(l)) { l.value.map { 2 * it } }
|
||||||
|
return ListValAndAdd(list) { l.add(4) }
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,7 @@ class QuestEditor(
|
|||||||
// Main Widget
|
// Main Widget
|
||||||
return QuestEditorWidget(
|
return QuestEditorWidget(
|
||||||
scope,
|
scope,
|
||||||
{ s -> QuestEditorToolbar(s, toolbarController) },
|
{ s -> QuestEditorToolbarWidget(s, toolbarController) },
|
||||||
{ s -> QuestInfoWidget(s, questInfoController) },
|
{ s -> QuestInfoWidget(s, questInfoController) },
|
||||||
{ s -> NpcCountsWidget(s, npcCountsController) },
|
{ s -> NpcCountsWidget(s, npcCountsController) },
|
||||||
{ s -> QuestEditorRendererWidget(s, canvas, renderer) }
|
{ s -> QuestEditorRendererWidget(s, canvas, renderer) }
|
||||||
|
@ -11,7 +11,9 @@ import world.phantasmal.lib.fileFormats.quest.parseBinDatToQuest
|
|||||||
import world.phantasmal.lib.fileFormats.quest.parseQstToQuest
|
import world.phantasmal.lib.fileFormats.quest.parseQstToQuest
|
||||||
import world.phantasmal.observable.value.Val
|
import world.phantasmal.observable.value.Val
|
||||||
import world.phantasmal.observable.value.mutableVal
|
import world.phantasmal.observable.value.mutableVal
|
||||||
|
import world.phantasmal.observable.value.value
|
||||||
import world.phantasmal.web.questEditor.loading.QuestLoader
|
import world.phantasmal.web.questEditor.loading.QuestLoader
|
||||||
|
import world.phantasmal.web.questEditor.models.AreaModel
|
||||||
import world.phantasmal.web.questEditor.stores.AreaStore
|
import world.phantasmal.web.questEditor.stores.AreaStore
|
||||||
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
||||||
import world.phantasmal.web.questEditor.stores.convertQuestToModel
|
import world.phantasmal.web.questEditor.stores.convertQuestToModel
|
||||||
@ -20,6 +22,8 @@ import world.phantasmal.webui.readFile
|
|||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
class AreaAndLabel(val area: AreaModel, val label: String)
|
||||||
|
|
||||||
class QuestEditorToolbarController(
|
class QuestEditorToolbarController(
|
||||||
private val questLoader: QuestLoader,
|
private val questLoader: QuestLoader,
|
||||||
private val areaStore: AreaStore,
|
private val areaStore: AreaStore,
|
||||||
@ -31,6 +35,28 @@ class QuestEditorToolbarController(
|
|||||||
val resultDialogVisible: Val<Boolean> = _resultDialogVisible
|
val resultDialogVisible: Val<Boolean> = _resultDialogVisible
|
||||||
val result: Val<PwResult<*>?> = _result
|
val result: Val<PwResult<*>?> = _result
|
||||||
|
|
||||||
|
// Ensure the areas list is updated when entities are added or removed (the count in the
|
||||||
|
// label should update).
|
||||||
|
val areas: Val<List<AreaAndLabel>> = questEditorStore.currentQuest.flatMap { quest ->
|
||||||
|
quest?.let {
|
||||||
|
quest.entitiesPerArea.map { entitiesPerArea ->
|
||||||
|
areaStore.getAreasForEpisode(quest.episode).map { area ->
|
||||||
|
val entityCount = entitiesPerArea[area.id]
|
||||||
|
AreaAndLabel(area, area.name + (entityCount?.let { " ($it)" } ?: ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: value(emptyList())
|
||||||
|
}
|
||||||
|
val currentArea: Val<AreaAndLabel?> = areas.map(questEditorStore.currentArea) { areas, area ->
|
||||||
|
areas.find { it.area == area }
|
||||||
|
}
|
||||||
|
val areaSelectDisabled: Val<Boolean>
|
||||||
|
|
||||||
|
init {
|
||||||
|
val noQuestLoaded = questEditorStore.currentQuest.map { it == null }
|
||||||
|
areaSelectDisabled = noQuestLoaded
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun createNewQuest(episode: Episode) {
|
suspend fun createNewQuest(episode: Episode) {
|
||||||
questEditorStore.setCurrentQuest(
|
questEditorStore.setCurrentQuest(
|
||||||
convertQuestToModel(questLoader.loadDefaultQuest(episode), areaStore::getVariant)
|
convertQuestToModel(questLoader.loadDefaultQuest(episode), areaStore::getVariant)
|
||||||
@ -81,6 +107,10 @@ class QuestEditorToolbarController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCurrentArea(areaAndLabel: AreaAndLabel) {
|
||||||
|
questEditorStore.setCurrentArea(areaAndLabel.area)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun setCurrentQuest(quest: Quest) {
|
private suspend fun setCurrentQuest(quest: Quest) {
|
||||||
questEditorStore.setCurrentQuest(convertQuestToModel(quest, areaStore::getVariant))
|
questEditorStore.setCurrentQuest(convertQuestToModel(quest, areaStore::getVariant))
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ abstract class QuestEntityModel<Type : EntityType, Entity : QuestEntity<Type>>(
|
|||||||
) {
|
) {
|
||||||
private val _sectionId = mutableVal(entity.sectionId)
|
private val _sectionId = mutableVal(entity.sectionId)
|
||||||
private val _section = mutableVal<SectionModel?>(null)
|
private val _section = mutableVal<SectionModel?>(null)
|
||||||
|
private val _sectionInitialized = mutableVal(false)
|
||||||
private val _position = mutableVal(vec3ToBabylon(entity.position))
|
private val _position = mutableVal(vec3ToBabylon(entity.position))
|
||||||
private val _worldPosition = mutableVal(_position.value)
|
private val _worldPosition = mutableVal(_position.value)
|
||||||
private val _rotation = mutableVal(vec3ToBabylon(entity.rotation))
|
private val _rotation = mutableVal(vec3ToBabylon(entity.rotation))
|
||||||
@ -30,6 +31,7 @@ abstract class QuestEntityModel<Type : EntityType, Entity : QuestEntity<Type>>(
|
|||||||
val sectionId: Val<Int> = _sectionId
|
val sectionId: Val<Int> = _sectionId
|
||||||
|
|
||||||
val section: Val<SectionModel?> = _section
|
val section: Val<SectionModel?> = _section
|
||||||
|
val sectionInitialized: Val<Boolean> = _sectionInitialized
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Section-relative position
|
* Section-relative position
|
||||||
@ -57,6 +59,12 @@ abstract class QuestEntityModel<Type : EntityType, Entity : QuestEntity<Type>>(
|
|||||||
|
|
||||||
setPosition(position.value)
|
setPosition(position.value)
|
||||||
setRotation(rotation.value)
|
setRotation(rotation.value)
|
||||||
|
|
||||||
|
setSectionInitialized()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSectionInitialized() {
|
||||||
|
_sectionInitialized.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPosition(pos: Vector3) {
|
fun setPosition(pos: Vector3) {
|
||||||
|
@ -2,5 +2,17 @@ package world.phantasmal.web.questEditor.models
|
|||||||
|
|
||||||
import world.phantasmal.lib.fileFormats.quest.ObjectType
|
import world.phantasmal.lib.fileFormats.quest.ObjectType
|
||||||
import world.phantasmal.lib.fileFormats.quest.QuestObject
|
import world.phantasmal.lib.fileFormats.quest.QuestObject
|
||||||
|
import world.phantasmal.observable.value.Val
|
||||||
|
import world.phantasmal.observable.value.mutableVal
|
||||||
|
|
||||||
class QuestObjectModel(obj: QuestObject) : QuestEntityModel<ObjectType, QuestObject>(obj)
|
class QuestObjectModel(obj: QuestObject) : QuestEntityModel<ObjectType, QuestObject>(obj) {
|
||||||
|
private val _model = mutableVal(obj.model)
|
||||||
|
|
||||||
|
val model: Val<Int?> = _model
|
||||||
|
|
||||||
|
fun setModel(model: Int) {
|
||||||
|
_model.value = model
|
||||||
|
|
||||||
|
// TODO: Propagate to props.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import world.phantasmal.web.externals.babylon.TransformNode
|
|||||||
import world.phantasmal.web.questEditor.loading.EntityAssetLoader
|
import world.phantasmal.web.questEditor.loading.EntityAssetLoader
|
||||||
import world.phantasmal.web.questEditor.models.QuestEntityModel
|
import world.phantasmal.web.questEditor.models.QuestEntityModel
|
||||||
import world.phantasmal.web.questEditor.models.QuestNpcModel
|
import world.phantasmal.web.questEditor.models.QuestNpcModel
|
||||||
|
import world.phantasmal.web.questEditor.models.QuestObjectModel
|
||||||
import world.phantasmal.web.questEditor.models.WaveModel
|
import world.phantasmal.web.questEditor.models.WaveModel
|
||||||
import world.phantasmal.web.questEditor.rendering.conversion.EntityMetadata
|
import world.phantasmal.web.questEditor.rendering.conversion.EntityMetadata
|
||||||
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
||||||
@ -117,8 +118,10 @@ class EntityMeshManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun load(entity: QuestEntityModel<*, *>) {
|
private suspend fun load(entity: QuestEntityModel<*, *>) {
|
||||||
// TODO
|
val mesh = entityAssetLoader.loadMesh(
|
||||||
val mesh = entityAssetLoader.loadMesh(entity.type, model = null)
|
type = entity.type,
|
||||||
|
model = (entity as? QuestObjectModel)?.model?.value
|
||||||
|
)
|
||||||
|
|
||||||
// Only add an instance of this mesh if the entity is still in the queue at this point.
|
// Only add an instance of this mesh if the entity is still in the queue at this point.
|
||||||
if (queue.remove(entity)) {
|
if (queue.remove(entity)) {
|
||||||
@ -132,48 +135,53 @@ class EntityMeshManager(
|
|||||||
loadedEntities[entity] = LoadedEntity(entity, instance, questEditorStore.selectedWave)
|
loadedEntities[entity] = LoadedEntity(entity, instance, questEditorStore.selectedWave)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private class LoadedEntity(
|
private inner class LoadedEntity(
|
||||||
entity: QuestEntityModel<*, *>,
|
entity: QuestEntityModel<*, *>,
|
||||||
val mesh: AbstractMesh,
|
val mesh: AbstractMesh,
|
||||||
selectedWave: Val<WaveModel?>,
|
selectedWave: Val<WaveModel?>,
|
||||||
) : DisposableContainer() {
|
) : DisposableContainer() {
|
||||||
init {
|
init {
|
||||||
mesh.metadata = EntityMetadata(entity)
|
mesh.metadata = EntityMetadata(entity)
|
||||||
|
|
||||||
observe(entity.worldPosition) { pos ->
|
observe(entity.worldPosition) { pos ->
|
||||||
mesh.position = pos
|
mesh.position = pos
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(entity.worldRotation) { rot ->
|
observe(entity.worldRotation) { rot ->
|
||||||
mesh.rotation = rot
|
mesh.rotation = rot
|
||||||
}
|
}
|
||||||
|
|
||||||
addDisposables(
|
val isVisible: Val<Boolean>
|
||||||
// TODO: Model.
|
|
||||||
// entity.model.observe {
|
|
||||||
// remove(listOf(entity))
|
|
||||||
// add(listOf(entity))
|
|
||||||
// },
|
|
||||||
)
|
|
||||||
|
|
||||||
if (entity is QuestNpcModel) {
|
if (entity is QuestNpcModel) {
|
||||||
addDisposable(
|
isVisible =
|
||||||
selectedWave
|
entity.sectionInitialized.map(
|
||||||
.map(entity.wave) { sWave, entityWave ->
|
selectedWave,
|
||||||
sWave == null || sWave == entityWave
|
entity.wave
|
||||||
|
) { sectionInitialized, sWave, entityWave ->
|
||||||
|
sectionInitialized && (sWave == null || sWave == entityWave)
|
||||||
}
|
}
|
||||||
.observe(callNow = true) { (visible) ->
|
} else {
|
||||||
mesh.setEnabled(visible)
|
isVisible = entity.section.map { section -> section != null }
|
||||||
},
|
|
||||||
)
|
if (entity is QuestObjectModel) {
|
||||||
|
addDisposable(entity.model.observe(callNow = false) {
|
||||||
|
remove(listOf(entity))
|
||||||
|
add(listOf(entity))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(isVisible) { visible ->
|
||||||
|
mesh.setEnabled(visible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun internalDispose() {
|
||||||
|
mesh.parent = null
|
||||||
|
mesh.dispose()
|
||||||
|
super.internalDispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun internalDispose() {
|
|
||||||
mesh.parent = null
|
|
||||||
mesh.dispose()
|
|
||||||
super.internalDispose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,12 @@ class QuestEditorMeshManager(
|
|||||||
private fun getAreaVariantDetails(quest: QuestModel?, area: AreaModel?): AreaVariantDetails {
|
private fun getAreaVariantDetails(quest: QuestModel?, area: AreaModel?): AreaVariantDetails {
|
||||||
quest?.let {
|
quest?.let {
|
||||||
val areaVariant = area?.let {
|
val areaVariant = area?.let {
|
||||||
quest.areaVariants.value.find { it.area.id == area.id }
|
quest.areaVariants.value.find { it.area.id == area.id } ?: area.areaVariants.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
areaVariant?.let {
|
areaVariant?.let {
|
||||||
val npcs = quest.npcs // TODO: Filter NPCs.
|
val npcs = quest.npcs.filtered { it.areaId == area.id }
|
||||||
val objects = quest.objects // TODO: Filter objects.
|
val objects = quest.objects.filtered { it.areaId == area.id }
|
||||||
return AreaVariantDetails(quest.episode, areaVariant, npcs, objects)
|
return AreaVariantDetails(quest.episode, areaVariant, npcs, objects)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
|||||||
abstract class QuestMeshManager protected constructor(
|
abstract class QuestMeshManager protected constructor(
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
questEditorStore: QuestEditorStore,
|
questEditorStore: QuestEditorStore,
|
||||||
private val renderer: QuestRenderer,
|
renderer: QuestRenderer,
|
||||||
areaAssetLoader: AreaAssetLoader,
|
areaAssetLoader: AreaAssetLoader,
|
||||||
entityAssetLoader: EntityAssetLoader,
|
entityAssetLoader: EntityAssetLoader,
|
||||||
) : TrackedDisposable() {
|
) : TrackedDisposable() {
|
||||||
@ -46,12 +46,14 @@ abstract class QuestMeshManager protected constructor(
|
|||||||
) {
|
) {
|
||||||
loadJob?.cancel()
|
loadJob?.cancel()
|
||||||
loadJob = scope.launch {
|
loadJob = scope.launch {
|
||||||
areaMeshManager.load(episode, areaVariant)
|
// Reset models.
|
||||||
|
|
||||||
areaDisposer.disposeAll()
|
areaDisposer.disposeAll()
|
||||||
npcMeshManager.removeAll()
|
npcMeshManager.removeAll()
|
||||||
objectMeshManager.removeAll()
|
objectMeshManager.removeAll()
|
||||||
|
|
||||||
|
// Load area model.
|
||||||
|
areaMeshManager.load(episode, areaVariant)
|
||||||
|
|
||||||
// Load entity meshes.
|
// Load entity meshes.
|
||||||
areaDisposer.addAll(
|
areaDisposer.addAll(
|
||||||
npcs.observeList(callNow = true, ::npcsChanged),
|
npcs.observeList(callNow = true, ::npcsChanged),
|
||||||
|
@ -24,11 +24,12 @@ class QuestEditorStore(scope: CoroutineScope, private val areaStore: AreaStore)
|
|||||||
val questEditingDisabled: Val<Boolean> = currentQuest.map { it == null }
|
val questEditingDisabled: Val<Boolean> = currentQuest.map { it == null }
|
||||||
|
|
||||||
suspend fun setCurrentQuest(quest: QuestModel?) {
|
suspend fun setCurrentQuest(quest: QuestModel?) {
|
||||||
_currentArea.value = null
|
if (quest == null) {
|
||||||
_currentQuest.value = quest
|
_currentArea.value = null
|
||||||
|
_currentQuest.value = null
|
||||||
quest?.let {
|
} else {
|
||||||
_currentArea.value = areaStore.getArea(quest.episode, 0)
|
_currentArea.value = areaStore.getArea(quest.episode, 0)
|
||||||
|
_currentQuest.value = quest
|
||||||
|
|
||||||
// Load section data.
|
// Load section data.
|
||||||
quest.areaVariants.value.forEach { variant ->
|
quest.areaVariants.value.forEach { variant ->
|
||||||
@ -37,6 +38,10 @@ class QuestEditorStore(scope: CoroutineScope, private val areaStore: AreaStore)
|
|||||||
setSectionOnQuestEntities(quest.npcs.value, variant, sections)
|
setSectionOnQuestEntities(quest.npcs.value, variant, sections)
|
||||||
setSectionOnQuestEntities(quest.objects.value, variant, sections)
|
setSectionOnQuestEntities(quest.objects.value, variant, sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure all entities have their section initialized.
|
||||||
|
quest.npcs.value.forEach { it.setSectionInitialized() }
|
||||||
|
quest.objects.value.forEach { it.setSectionInitialized() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +56,7 @@ class QuestEditorStore(scope: CoroutineScope, private val areaStore: AreaStore)
|
|||||||
|
|
||||||
if (section == null) {
|
if (section == null) {
|
||||||
logger.warn { "Section ${entity.sectionId.value} not found." }
|
logger.warn { "Section ${entity.sectionId.value} not found." }
|
||||||
|
entity.setSectionInitialized()
|
||||||
} else {
|
} else {
|
||||||
entity.setSection(section)
|
entity.setSection(section)
|
||||||
}
|
}
|
||||||
@ -58,6 +64,13 @@ class QuestEditorStore(scope: CoroutineScope, private val areaStore: AreaStore)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCurrentArea(area: AreaModel?) {
|
||||||
|
// TODO: Set wave.
|
||||||
|
|
||||||
|
_selectedEntity.value = null
|
||||||
|
_currentArea.value = area
|
||||||
|
}
|
||||||
|
|
||||||
fun setSelectedEntity(entity: QuestEntityModel<*, *>?) {
|
fun setSelectedEntity(entity: QuestEntityModel<*, *>?) {
|
||||||
entity?.let {
|
entity?.let {
|
||||||
currentQuest.value?.let { quest ->
|
currentQuest.value?.let { quest ->
|
||||||
|
@ -7,12 +7,9 @@ import world.phantasmal.lib.fileFormats.quest.Episode
|
|||||||
import world.phantasmal.web.questEditor.controllers.QuestEditorToolbarController
|
import world.phantasmal.web.questEditor.controllers.QuestEditorToolbarController
|
||||||
import world.phantasmal.webui.dom.Icon
|
import world.phantasmal.webui.dom.Icon
|
||||||
import world.phantasmal.webui.dom.div
|
import world.phantasmal.webui.dom.div
|
||||||
import world.phantasmal.webui.widgets.Button
|
import world.phantasmal.webui.widgets.*
|
||||||
import world.phantasmal.webui.widgets.FileButton
|
|
||||||
import world.phantasmal.webui.widgets.Toolbar
|
|
||||||
import world.phantasmal.webui.widgets.Widget
|
|
||||||
|
|
||||||
class QuestEditorToolbar(
|
class QuestEditorToolbarWidget(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
private val ctrl: QuestEditorToolbarController,
|
private val ctrl: QuestEditorToolbarController,
|
||||||
) : Widget(scope) {
|
) : Widget(scope) {
|
||||||
@ -36,6 +33,14 @@ class QuestEditorToolbar(
|
|||||||
accept = ".bin, .dat, .qst",
|
accept = ".bin, .dat, .qst",
|
||||||
multiple = true,
|
multiple = true,
|
||||||
filesSelected = { files -> scope.launch { ctrl.openFiles(files) } }
|
filesSelected = { files -> scope.launch { ctrl.openFiles(files) } }
|
||||||
|
),
|
||||||
|
Select(
|
||||||
|
scope,
|
||||||
|
disabled = ctrl.areaSelectDisabled,
|
||||||
|
itemsVal = ctrl.areas,
|
||||||
|
itemToString = { it.label },
|
||||||
|
selectedVal = ctrl.currentArea,
|
||||||
|
onSelect = ctrl::setCurrentArea
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
))
|
))
|
@ -37,8 +37,7 @@ class Select<T : Any>(
|
|||||||
private val items: Val<List<T>> = itemsVal ?: value(items ?: emptyList())
|
private val items: Val<List<T>> = itemsVal ?: value(items ?: emptyList())
|
||||||
private val selected: Val<T?> = selectedVal ?: value(selected)
|
private val selected: Val<T?> = selectedVal ?: value(selected)
|
||||||
|
|
||||||
// Default to a single space so the inner text part won't be hidden.
|
private val buttonText = mutableVal(" ")
|
||||||
private val buttonText = mutableVal(this.selected.value?.let(itemToString) ?: " ")
|
|
||||||
private val menuHidden = mutableVal(true)
|
private val menuHidden = mutableVal(true)
|
||||||
|
|
||||||
private lateinit var menu: Menu<T>
|
private lateinit var menu: Menu<T>
|
||||||
@ -48,6 +47,9 @@ class Select<T : Any>(
|
|||||||
div {
|
div {
|
||||||
className = "pw-select"
|
className = "pw-select"
|
||||||
|
|
||||||
|
// Default to a single space so the inner text part won't be hidden.
|
||||||
|
observe(selected) { buttonText.value = it?.let(itemToString) ?: " " }
|
||||||
|
|
||||||
addWidget(Button(
|
addWidget(Button(
|
||||||
scope,
|
scope,
|
||||||
disabled = disabled,
|
disabled = disabled,
|
||||||
|
Loading…
Reference in New Issue
Block a user