firstrateconcepts / FusionOfSouls

Fusion of Souls is a Roguelike Auto-Battler that adds a new level of difficult decision-making by allowing you to fuse units and items into a powerful hero opening up a whole new layer of strategy to the genre.
GNU General Public License v3.0
1 stars 0 forks source link

New Eventing/Hooking system #29

Open runt9 opened 3 years ago

runt9 commented 3 years ago

longer description tbd

Need to differentiate between "I need to update/redraw when X happens", "I need to perform X action when Y happens", and "X thing is happening, allow Y and Z to modify X before processing it"

runt9 commented 3 years ago

Example of a way the hooking could work with moving to Ashley

interface UnitHook {
    fun beforeAttack(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damageCalcRequest: DamageCalcRequest) {}
    fun whenHit(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damage: DamageCalcResult) {}
    fun onHit(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damage: DamageCalcResult) {}
}

class UnitHooksComponent : Component, UnitHook {
    val hooks = mutableListOf<UnitHook>()

    override fun beforeAttack(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damageCalcRequest: DamageCalcRequest) =
        hooks.forEach { it.beforeAttack(attacker, defender, attackRoll, critResult, damageCalcRequest) }

    override fun whenHit(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damage: DamageCalcResult) =
        hooks.forEach { it.whenHit(attacker, defender, attackRoll, critResult, damage) }

    override fun onHit(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damage: DamageCalcResult) =
        hooks.forEach { it.onHit(attacker, defender, attackRoll, critResult, damage) }
}

Adding a hook:

entity[unitHooksMapper]?.hooks?.add(object : UnitHook {
    override fun beforeAttack(attacker: BattleUnit, defender: BattleUnit, attackRoll: AttackRollResult, critResult: CritCheckResult, damageCalcRequest: DamageCalcRequest) {
        // do stuff
    }
})

System that is handling attacks:

class AttackSystem : IteratingSystem(oneOf(CanAttackComponent::class).get()) {
    override fun processEntity(entity: Entity, deltaTime: Float) {
        val hooks = entity[unitHooksMapper]
        hooks?.beforeAttack(attacker, defender, attackRoll, critResult, damageCalcRequest)
        // attack happens
        // damage happens
        hooks?.onHit(attacker, defender, attackRoll, critResult, damage)
        hooks?.whenHit(attacker, defender, attackRoll, critResult, damage)
    }
}
runt9 commented 3 years ago

Here's an example of what can be done with an AutoUpdatingLabel + Observable + ViewModel pattern:

object AutoUpdatingLabelTest {
    @JvmStatic
    fun main(args: Array<String>) {
        val vm = TopBarViewModel(0, 0, 3, 1, 1)
        val label = AutoUpdatingLabel { "Gold: ${vm.gold()}" }
        println(label.text)
        vm.gold += 3
        println(label.text)
    }
}

private operator fun Observable<Int>.plusAssign(t: Int) {
    set(get() + t)
}

interface Updateable {
    fun update()

    operator fun <T : Any> Observable<T>.invoke(): T {
        bind(this@Updateable)
        return get()
    }
}

class Observable<T : Any>(initialValue: T) {
    private var realValue = initialValue
    private val binds = mutableSetOf<Updateable>()

    operator fun invoke(value: T) = set(value)

    fun set(value: T) {
        if (value == realValue) return
        realValue = value
        binds.forEach(Updateable::update)
    }

    fun get() = realValue

    fun bind(updateable: Updateable) = binds.add(updateable)
}

class TopBarViewModel(gold: Int, activeUnitCount: Int, unitCap: Int, floor: Int, room: Int) {
    val gold = Observable(gold)
    val activeUnitCount = Observable(activeUnitCount)
    val unitCap = Observable(unitCap)
    val floor = Observable(floor)
    val room = Observable(room)
}

class AutoUpdatingLabel(val strGetter: AutoUpdatingLabel.() -> String) : Updateable {
    var text: String
    init {
        text = strGetter()
    }

    override fun update() {
        text = strGetter()
    }
}
runt9 commented 3 years ago

With the example above, the following is a concept for data flow for updating UI elements based off of events occurring:

runt9 commented 3 years ago

Event bus added in 94d2adc74adf28376a4a23b70fb69bb723245fbc

runt9 commented 3 years ago

Eventing and ViewModel auto-updating scaffolding finished in 57dbf55bc2b33a1aee58967cfca785b6471b70a5