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