• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Demongo

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: ArchDuke
Demongo The Soul Collector

Demongo is a character in the SJ universe.
Soul model and movement code from Rift Stalker's Soul Conjure ability from TcX AoS by Toadcop.

JASS:
library Demongo requires Ht, DummyUnit, TimedEffect, v3

private function dd takes string s returns nothing
    call BJDebugMsg(s)
endfunction

globals
    private constant string SOUL_MODEL = "models\\soul-conjure-missile.mdx"
    private constant string SOUL_SUMMONED_MODEL = "Abilities\\Spells\\Orc\\Disenchant\\DisenchantSpecialArt.mdl"
    private constant real SOUL_DT = 1.0 / 32.0

    private constant integer DEMONGO_SOUL_COLLECTION_ID = 'A003'

//! textmacro DEMONGO_IDS
    // odd: unit-type-id, even: ability-id
    set ids[1] = 'hfoo'
    set ids[2] = 'A001'

    set ids[3] = 'ogru'
    set ids[4] = 'A002'

    set ids[5] = 'n001'
    set ids[6] = 'A004'

    set ids[7] = 'n002'
    set ids[8] = 'A005'

    set ids[9] = 'n003'
    set ids[10] = 'A006'

    set ids[11] = 'n004'
    set ids[12] = 'A007'

    set ids[13] = 'n005'
    set ids[14] = 'A008'

    set ids[15] = 'n006'
    set ids[16] = 'A009'

    set ids[17] = 'n007'
    set ids[18] = 'A00A'

    set ids[19] = 'n008'
    set ids[20] = 'A00B'

    set ids[21] = 'n009'
    set ids[22] = 'A00C'

    set ids[23] = 'n00A'
    set ids[24] = 'A00D'

    set ids_count = 24
//! endtextmacro

endglobals

private keyword Demongo

private function targets_allowed takes Demongo dem, unit dying_unit returns boolean
    // we already check for the above unit-type-ids
    // return IsUnitEnemy(dying_unit, GetOwningPlayer(dem.d_u))
    return true
endfunction

globals
    private constant real tau = 2.0 * (355.0 / 113.0)
    private integer array abis
    private integer abis_count = 0

    private real map_min_x
    private real map_max_x
    private real map_min_y
    private real map_max_y
endglobals

private struct Demongo
    static Ht abi_from_uid
    static Ht uid_from_abi
    static Ht dem_from_unit
    static Ht souls_count_of_type

    // all Demongos (if one decides to clone him for example) are in this list
    static thistype dems

    // d_ = Demongo
    thistype d_prev
    thistype d_next
    unit d_u

    // Demongo can have a list of souls that follow him around
    // s_ = Soul
    static timer s_tmr = CreateTimer()
    static boolean s_tmr_ticking = false
    thistype s_prev
    thistype s_next
    v3 s_p
    v3 s_dp
    v3 s_ddp
    real s_accel
    real s_accelmult
    v3 s_a
    v3 s_b
    real s_dist_xy
    real s_dist_xyz
    real s_t
    real s_t_inc
    thistype s_dem
    DummyUnit s_du
    effect s_ef_a // because the soul model is a bit dim
    effect s_ef_b // we use 2 at the same time
    static constant integer Soul_State_Follow_Demongo = 1
    static constant integer Soul_State_Summoned = 2
    static constant integer Soul_State_Goto_Heaven = 3
    integer s_state
    unit s_tar
    integer s_summon_uid

    static code on_unit_death_handler
    static code soul_update_handler
    static code on_spell_effect_handler
    private static method onInit takes nothing returns nothing
        local integer array ids
        local integer ids_count
        local trigger t
        local integer p
        local integer i
        local rect r

        call ExecuteFunc("s__" + "thistype" + "_set_handlers")

        set r = GetWorldBounds()
        set map_min_x = GetRectMinX(r)
        set map_max_x = GetRectMaxX(r)
        set map_min_y = GetRectMinY(r)
        set map_max_y = GetRectMaxY(r)
        call RemoveRect(r)
        set r = null

        set abi_from_uid = Ht.create()
        set uid_from_abi = Ht.create()
        set dem_from_unit = Ht.create()
        set souls_count_of_type = Ht.create()

        set dems = allocate()
        set dems.d_prev = dems
        set dems.d_next = dems

        //! runtextmacro DEMONGO_IDS()

        set i = 1
        loop
            exitwhen i > ids_count

            set abi_from_uid.select()[ids[i]].int = ids[i + 1]
            set uid_from_abi.select()[ids[i + 1]].int = ids[i]

            set abis_count = abis_count + 1
            set abis[abis_count] = ids[i + 1]

            set p = 0
            loop
                exitwhen p >= bj_MAX_PLAYER_SLOTS
                call SetPlayerAbilityAvailable(Player(p), ids[i + 1], false)
                set p = p + 1
            endloop

            set i = i + 2
        endloop

        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddAction(t, on_unit_death_handler)

        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, on_spell_effect_handler)
    endmethod

    static method modify_souls_count_of_type_for takes thistype dem, integer uid, integer value returns integer
        local integer new_count
        call souls_count_of_type.select()

        set new_count = souls_count_of_type[integer(dem) * 8190 + uid].int + value
        set souls_count_of_type[integer(dem) * 8190 + uid].int = new_count
        return new_count
    endmethod

    static method reset_souls_count_of_type_for takes thistype dem, integer uid returns nothing
        call souls_count_of_type.select()
        set souls_count_of_type[integer(dem) * 8190 + uid].int = 0
    endmethod

    static method Demongo_create takes unit u_dem returns thistype
        local thistype dem = allocate()

        set dem.d_prev = dems.d_prev
        set dem.d_next = dems
        set dem.d_prev.d_next = dem
        set dem.d_next.d_prev = dem

        set dem.s_prev = dem
        set dem.s_next = dem

        set dem.d_u = u_dem

        return dem
    endmethod

    static method Soul_create takes thistype dem, unit u_dem, unit dying_unit returns thistype
        local thistype s = allocate()
        local unit u

        set s.s_prev = dem.s_prev
        set s.s_next = dem
        set s.s_prev.s_next = s
        set s.s_next.s_prev = s

        set s.s_p = v3_from_unit(dying_unit).to_owned()
        set s.s_dp = v3c(0.0, 0.0, 0.0).to_owned()
        set s.s_ddp = v3c(0.0, 0.0, 0.0).to_owned()
        set s.s_ddp.z = GetRandomReal(600.0, 900.0)
        set s.s_accel = GetRandomReal(1300.0, 1800.0)
        set s.s_accelmult = GetRandomReal(0.125,0.16)
        set s.s_a = v3c(0.0, 0.0, 0.0).to_owned()
        set s.s_b = v3c(0.0, 0.0, 0.0).to_owned()

        set s.s_dem = dem
        set s.s_du = DummyUnit.get().with_owner(GetPlayerId(GetOwningPlayer(u_dem)))
        set u = s.s_du.u
        set s.s_ef_a = AddSpecialEffectTarget(SOUL_MODEL, u, "origin")
        set s.s_ef_b = AddSpecialEffectTarget(SOUL_MODEL, u, "origin")
        call SetUnitScale(u, 1.1, 1.1, 1.1)

        set s.s_state = Soul_State_Follow_Demongo
        set s.s_tar = u_dem

        set s.s_summon_uid = 0

        if not s_tmr_ticking then
            set s_tmr_ticking = true
            call TimerStart(s_tmr, SOUL_DT, true, soul_update_handler)
        endif

        set u = null
        return s
    endmethod

    static method Soul_destroy takes thistype s returns nothing
        set s.s_prev.s_next = s.s_next
        set s.s_next.s_prev = s.s_prev

        call s.s_p.destroy()
        call s.s_dp.destroy()
        call s.s_ddp.destroy()
        call s.s_a.destroy()
        call s.s_b.destroy()
        call s.s_du.return_instantly()
        call DestroyEffect(s.s_ef_a)
        set s.s_ef_a = null
        call DestroyEffect(s.s_ef_b)
        set s.s_ef_b = null
        set s.s_tar = null
        call s.destroy()
    endmethod

    static method on_unit_death takes nothing returns nothing
        local unit killing_unit = GetKillingUnit()
        local unit dying_unit = GetTriggerUnit() // GetDyingUnit()
        local integer abi
        local integer uid
        local thistype dem
        local thistype s // soul
        local v3 v
        local player pp
        local integer i
        local boolean killing_unit_is_demongo
        local boolean dying_unit_is_demongo

        set killing_unit_is_demongo = GetUnitAbilityLevel(killing_unit, DEMONGO_SOUL_COLLECTION_ID) > 0
        if killing_unit_is_demongo then
            set dem = dem_from_unit.select()[Ht.h2i(killing_unit)].int
            if dem == 0 then
                set dem = Demongo_create(killing_unit)
                set dem_from_unit[Ht.h2i(killing_unit)].int = dem
            endif

            set uid = GetUnitTypeId(dying_unit)
            set abi = abi_from_uid.select()[uid].int
            if abi != 0 and targets_allowed(dem, dying_unit) then
                call SetPlayerAbilityAvailable(GetOwningPlayer(killing_unit), abi, true)
                set s = Soul_create(dem, killing_unit, dying_unit)
                call modify_souls_count_of_type_for(dem, uid, 1)
            endif
        endif

        set dying_unit_is_demongo = GetUnitAbilityLevel(dying_unit, DEMONGO_SOUL_COLLECTION_ID) > 0
        if dying_unit_is_demongo then
            set dem = dem_from_unit.select()[Ht.h2i(dying_unit)].int
            if dem == 0 then
                set dem = Demongo_create(dying_unit)
                set dem_from_unit[Ht.h2i(dying_unit)].int = dem
            endif

            set s = dem.s_next
            loop
                exitwhen s == dem

                set s.s_state = Soul_State_Goto_Heaven
                set s.s_tar = DummyUnit.get().u

                // heaven is 2048.0 units above =)
                set v = v3c(GetRandomReal(-600.0, 600.0), GetRandomReal(-600.0, 600.0), 2048.0)
                set v = v3a(v, v3_from_unit(s.s_dem.d_u))
                call SetUnitX(s.s_tar, v.x)
                call SetUnitY(s.s_tar, v.y)
                call MoveLocation(v3_loc, v.x, v.y)
                call SetUnitFlyHeight(s.s_tar, v.z - GetLocationZ(v3_loc), 0.0)

                call s.s_ddp.set_eq_to(v3c(0.0, 0.0, 0.0))
                set s.s_accel = s.s_accel / 12.0

                set s = s.s_next
            endloop

            set pp = GetOwningPlayer(dying_unit)
            set i = 1
            loop
                exitwhen i > abis_count
                call SetPlayerAbilityAvailable(pp, abis[i], false)
                call reset_souls_count_of_type_for(dem, uid_from_abi.select()[abis[i]])
                set i = i + 1
            endloop
        endif

        set killing_unit = null
        set dying_unit = null
    endmethod

    static method on_spell_effect takes nothing returns nothing
        local integer abi
        local integer uid
        local thistype dem
        local thistype s // soul
        local v3 a
        local v3 b
        local v3 ab
        local v3 c

        set abi = GetSpellAbilityId()
        set uid = uid_from_abi.select()[abi].int
        if uid == 0 then
            return
        endif

        set dem = dem_from_unit.select()[Ht.h2i(GetTriggerUnit())].int

        if modify_souls_count_of_type_for(dem, uid, -1) == 0 then
            call SetPlayerAbilityAvailable(GetOwningPlayer(dem.d_u), abi, false)
        endif

        // find the first soul that is not being summoned
        // there should always be at least one such soul
        set s = dem.s_next
        loop
            debug exitwhen s == dem

            if s.s_summon_uid == 0 then
                exitwhen true
            endif
            set s = s.s_next
        endloop

        // assert that there really is an available soul
static if DEBUG_MODE then
        if s == dem then
            call dd("soul bug!")
            if 1 / 0 == 1 then
            endif
        endif
endif

        set s.s_state = Soul_State_Summoned
        set a = v3_from_unit(s.s_dem.d_u)
        set b = v3_from_spell_target()
        set ab = v3s(b, a)
        set c = v3_from_polar(ab.ang_rot(), 24.0)
        set c.z = c.z + 48.0
        call s.s_a.set_eq_to(v3a(c, a))
        call s.s_b.set_eq_to(ab)
        set s.s_dist_xy = s.s_b.len_xy()
        set s.s_dist_xyz = s.s_b.len()
        set s.s_t = 0.0
        set s.s_t_inc = (900.0 / s.s_dist_xy) * SOUL_DT
        set s.s_summon_uid = uid
    endmethod

    static method soul_update takes nothing returns nothing
        local thistype dem
        local thistype s // soul
        local thistype next_soul
        local v3 vn
        local v3 v
        local unit u
        local real z_offset = 0.0
        local real d
        local boolean done

        set dem = dems.d_next
        loop
            exitwhen dem == dems

            set s = dem.s_next
            loop
                exitwhen s == dem
                set next_soul = s.s_next
                set done = false

                if s.s_state == Soul_State_Follow_Demongo or s.s_state == Soul_State_Goto_Heaven then
                    set vn = v3n(v3s(v3_from_unit(s.s_tar), s.s_p))

                    call s.s_ddp.set_eq_to(v3a(s.s_ddp, v3sc(s.s_accel * SOUL_DT, vn)))

                    set v = v3a(v3sc(s.s_accel * s.s_accelmult, vn), s.s_ddp)
                    call s.s_dp.set_eq_to(v3sc(SOUL_DT, v))

                    call s.s_p.set_eq_to(v3a(s.s_p, s.s_dp))

                    // Demongo scaled to 0.60 is about 90.0 units high and his torso is
                    // somewhere around 60.0
                    set z_offset = 60.0

                    if s.s_state == Soul_State_Goto_Heaven then
                        set z_offset = 0.0

                        if s.s_p.z > 2048.0 then
                            set done = true
                            call DummyUnit.from(s.s_tar).return_instantly()
                        endif
                    endif

                elseif s.s_state == Soul_State_Summoned then
                    if s.s_t >= 1.0 then
                        set done = true
                        set s.s_t = 1.0
                    endif

                    call s.s_p.set_eq_to(v3a(s.s_a, v3sc(s.s_t, s.s_b)))
                    set d = s.s_t * s.s_dist_xyz
                    set s.s_p.z = s.s_p.z + ((4.0 * s.s_dist_xy * 0.5 * d * (s.s_dist_xyz - d)) / (s.s_dist_xyz * s.s_dist_xyz))

                    if done then
                        call CreateUnit(GetOwningPlayer(s.s_dem.d_u), s.s_summon_uid, s.s_p.x, s.s_p.y, bj_RADTODEG * GetRandomReal(0.0, tau))
                        call TimedEffect/*
                            */.create(SOUL_SUMMONED_MODEL, 1.0)/*
                            */.set_xyz(s.s_p.x, s.s_p.y, 0.0)/*
                            */.show()

                        call TimedEffect/*
                            */.create("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", 1.0)/*
                            */.set_xyz(s.s_p.x, s.s_p.y, 0.0)/*
                            */.scale(0.2)/*
                            */.show()
                    else
                        set s.s_t = s.s_t + s.s_t_inc
                    endif

                    set z_offset = 0.0
                endif

                if s.s_p.x < map_min_x then
                    set s.s_p.x = map_min_x
                elseif s.s_p.x > map_max_x then
                    set s.s_p.x = map_max_x
                elseif s.s_p.y < map_min_y then
                    set s.s_p.y = map_min_y
                elseif s.s_p.y > map_max_y then
                    set s.s_p.y = map_max_y
                endif

                set u = s.s_du.u
                call SetUnitX(u, s.s_p.x)
                call SetUnitY(u, s.s_p.y)
                call MoveLocation(v3_loc, s.s_p.x, s.s_p.y)
                call SetUnitFlyHeight(u, s.s_p.z - GetLocationZ(v3_loc) + z_offset, 0.0)

                if done then
                    call Soul_destroy(s)
                endif

                set s = next_soul
            endloop

            set dem = dem.d_next
        endloop

        set u = null
    endmethod

    static method set_handlers takes nothing returns nothing
        set on_unit_death_handler = function thistype.on_unit_death
        set soul_update_handler = function thistype.soul_update
        set on_spell_effect_handler = function thistype.on_spell_effect
    endmethod

endstruct

endlibrary
Contents

Demongo (Map)

Reviews
IcemanBo
No explanations, or tooltip. No links or credits of resources. This is also because they are nowhere approved, but are probably your personal work. They might be good, but we should not require x "random" personal resources when have standard ones...
Level 5
Joined
May 2, 2015
Messages
109
Cool idea, Aniki.

But, I think this spell needs some more special effect, like when summoning the units.
And it will look much better if you increase the circle radius of the soul orbs. Make it random maybe.
Also this spell would be more fun if you add a ability to take back the summoned units.

Lastly, I want to understand more about your spell code.
Can you add some comment in the code about how this function work, how that number work.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I think this spell needs some more special effect, like when summoning the units.
It already creates 2 effects when units are summoned/created. I don't really like going to "crazy town" with special effects either.

And it will look much better if you increase the circle radius of the soul orbs.
Tried it but didn't like it as much. I prefer the "stickiness" of the souls instead of having them orbiting around like planets.

Also this spell would be more fun if you add a ability to take back the summoned units.
This is really a balance issue, currently (for the demo's sake) you can last hit your own units ( function targets_allowed) and have their souls back.
 
  • No explanations, or tooltip.

  • No links or credits of resources. This is also because they are nowhere approved, but are probably your personal work. They might be good, but we should not require x "random" personal resources when have standard ones. I see no sense to rely on new timer, dummy, and maths libs, which I don't know, if we can use approved ones.
I'm sad once again, but it's not confirm enough with standards that it can be currently approved.
 
Top