Skip to content

Immediate Game API

rmx edited this page May 30, 2016 · 1 revision

This pages discusses the change from the current scheduleEvent based global game API to an new, immediate API.

Old system

  • All actions are scheduled as new events in the event queue
  • Scripts (auras, behaviors) can filter and modify these events

Example:

class Fireball
    finishSpellcast: (target) ->
        eventSchedule 0, unitAttributeChangeBy target, 'health', -100

class Fireward
    filterEvent: (e) ->
        if e.origin.name is 'Fire Blast'
            # Revert the damage done by fire blast
            eventAppend e, unitAttributeChangeBy e.target, -e.args[2]

New system

  • Major change: all actions are immediately applied
  • Minor change: Use named parameters in API calls
  • Minor change: Built-in attributes like hp or mana have their own API

Example:

class Fireball
    onFinishSpellcast: (target) ->
        # Note how actions can have return values now
        damageDealt = damageUnit {unit:target, amount:100, type:'fire'}

class Fireward
    onDamageUnit: (e) ->
        # Convenience function that subtracts from e.data.amount and adds to e.data.absorbed
        damageAbsorbed = absorbDamage e, {amount:1000, type:'fire'}

A very rough sketch for how the damageUnit call is implemented:

damageUnit: (data) ->
    event = {name:'damageUnit', data:data}
    # Get a list of scripts that may filter this event, sorted by their priority
    scripts = getScriptsThatMayFilterThisEvent()
    scripts.sort (a, b) -> a.priority < b.priority
    # Allow the scripts to modify the damage amount
    for script in scripts
        script.onDamageUnit? event
    # Notify the client
    _addCombatLogEvent event
    # Apply the damage
    _changeUnitHp data.unit, -data.amount

Issues and open questions

  1. How should event filters look like?

    • As before (i.e., filterEvent)
      • You most likely have to check the event name
    • Named after the event (i.e., onDamageUnit)
      • Looks nice, easier to understand
      • More consistent with the behavior scripts
      • More difficult to implement a function that reacts to multiple events
  2. Stack overflow

    • If an event filter generates another event, the immediate evaluation can lead to a stack overflow
    • This corresponds to a queue with a never-ending stream of events with delay 0 in the old system
    • Possible solution: check the call stack depth in each global api call
  3. Reactions and the order of combat log events

    • If a direct damage triggers an immediate heal of the damaged unit (prayer of mending), how should those two events be reported and applied?
    • If all actions are immediately applied, this would result in the heal being applied before the damage
    • It is probably ok to immediately modify and apply the immediate effects of an action (i.e., immediately modify the unit hp in a damageUnit call)
    • It is probably not ok to immediately execute reactions to an action.
    • Possible solution: use something similar to the current eventAppend
    • Possible solution: actions like damageUnit cannot be executed from within event filters. Instead, event filters can register reaction callbacks, that will be executed after the event is finished. Note that this would also prevent the stack overflow problem. Example:
    class PrayerOfMendingAura
        onDamageUnit: (e) ->
            triggerReaction @triggerHeal
        triggerHeal: (e) ->
            healUnit {unit:e.data.unit, amount:100}
            removeAura {unit:e.data.unit, name:'PoM'}
            addAura {unit:getNearbyUnit(), name:'PoM'}

    Tom: We can avoid these problems if we separate the two, absorbing incoming damage and reacting to incoming damage. Eg:

    class PrayerOfMending
    
        # Called by the engine after the unit takes damage. The actual amount of damage,
        # how much was absorbed, resisted etc is known at this point.
        # We can define either specific callbacks (onDamage, onHeal, onXyz) or have a more
        # generic handleEvent() which will be given the same Event instance as behaviors. 
        onDamage: (...) ->
           healUnit ...
    class FireWard
    
        # Called by the engine when the unit is about to take damage, to allow
        # the script to absorb, resist etc.
        modifyIncomingDamage: (x) ->
            absorbDamage x, 20
  4. Delayed actions

    • Currently, none of our encounter need delayed actions (apart from stuff like the removal of expired auras or ticking auras)
    • Possible solution: we just do not support it
      • Use short-duration auras that do something when they expire
      • Use projectiles with a flight duration that do something on impact
  5. Backward compatibility

    • Should we keep the two systems in parallel until we rewrite all encounters?
Clone this wiki locally