Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Ht
//! novjass
// Usage:
globals
Ht ht
endglobals
function main takes nothing returns nothing
local integer hid
local integer my_int
// on init
set ht = Ht.create()
// in "create"
call ht.select()
set hid = ht.h2i(/*CreateTrigger()*/ null)
set my_int = 42
set ht[hid].int = my_int
// inlined:
set ht.select()[ht.h2i(/*CreateTrigger()*/ null)].int = 42
// in "handler"
call ht.select()
set hid = ht.h2i(/*GetTriggeringTrigger()*/ null)
set my_int = ht[hid].int
// inlined:
set my_int = ht.select()[ht.h2i(/*CreateTrigger()*/ null)].int
if ht[hid].int_e() then
call BJDebugMsg(I2S(my_int))
call ht[hid].int_d()
if not ht[hid].int_e() then
call BJDebugMsg("removed " + I2S(my_int))
endif
endif
endfunction
//! endnovjass
globals
private hashtable ht = InitHashtable()
endglobals
struct Ht
private static integer pk1
private static integer pk2
static method h2i takes handle h returns integer
return GetHandleId(h)
endmethod
static method s2i takes string s returns integer
return StringHash(s)
endmethod
// set the parent key, and make 2 copies of it
method select takes nothing returns thistype
set pk1 = this
set pk2 = this
return this
endmethod
method operator[] takes integer i returns thistype
return this + i
endmethod
method operator int= takes integer x returns nothing
call SaveInteger(ht, pk1, this - pk2, x) // this - pk2 = i
endmethod
method operator int takes nothing returns integer
return LoadInteger(ht, pk1, this - pk2)
endmethod
method int_e takes nothing returns boolean // int-exists?
return HaveSavedInteger(ht, pk1, this - pk2)
endmethod
method int_d takes nothing returns nothing // int-delete/destroy
call RemoveSavedInteger(ht, pk1, this - pk2)
endmethod
endstruct
endlibrary
//TESH.scrollpos=104
//TESH.alwaysfold=0
library Timers
// Credits:
// the guys from xgm.guru (the russian modding community) for finding (back in 2007?) the method
// of attaching data to timers used by this library: http://xgm.guru/p/wc3/timer-exploit
//! novjass
// starting/creating a timer
//
static method start takes integer user_data, real timeout, code callback returns Timer
// stopping/releasing a timer
//
method stop takes nothing returns nothing
// pausing a timer
//
method pause takes nothing returns nothing
// there are two methods which give access to the data within the body of the callback function:
//
// the first is:
// Timer.get_expired_data, which is intended to be used by oneshot timers
//
// the second is:
// Timer.get_expired, which is intended to be used by periodic timers
//
// returns the data and automatically stops/releases the timer
//
static method get_expired_data takes nothing returns integer
// returns the timer instance which has the .data member with which you can access the data,
// but does NOT automatically stop/release the timer, i.e you have to do it manually (at some point)
//
static method get_expired takes nothing returns Timer
// this method needs to be called for periodic timers
// NOTE: the callback has to be passed again (because we can't store code variables in arrays, this is a Jass2 limitation)
//
method restart takes code callback returns nothing
//! endnovjass
struct Timer extends array
readonly static integer max_count = 0 // the maximum number of timers we've ever had ticking
readonly static integer curr_count = 0 // the current number of timers ticking
private static Timer head = 0 // a free list of timers
private Timer next
private timer t
integer data
real timeout
static method start takes integer user_data, real timeout, code callback returns Timer
local Timer this
if head != 0 then
set this = head
set head = head.next
else
set max_count = max_count + 1
if max_count > 8190 then
static if DEBUG_MODE then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: could not allocate a Timer instance|r")
endif
return 0
endif
set this = max_count
set this.t = CreateTimer()
endif
set curr_count = curr_count + 1
set this.next = 0
set this.data = user_data
set this.timeout = timeout
call TimerStart(this.t, this, false, null)
call PauseTimer(this.t)
call TimerStart(this.t, timeout, false, callback)
return this
endmethod
method stop takes nothing returns nothing
if this == 0 then
static if DEBUG_MODE then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop null Timer instance|r")
endif
return
endif
if this.next != 0 then
static if DEBUG_MODE then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop Timer(" + I2S(this) + ") instance more than once|r")
endif
return
endif
set curr_count = curr_count - 1
call TimerStart(this.t, 0.0, false, null)
set this.next = head
set head = this
endmethod
method pause takes nothing returns nothing
call TimerStart(this.t, 0.0, false, null)
endmethod
static method get_expired takes nothing returns Timer
return R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5)
endmethod
static method get_expired_data takes nothing returns integer
local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
local integer data = t.data
call t.stop()
return data
endmethod
method restart takes code callback returns nothing
call TimerStart(this.t, this.data, false, null)
call PauseTimer(this.t)
call TimerStart(this.t, this.timeout, false, callback)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library DummyUnit
//! novjass
General notes:
Dummy units come up often because they are used for casting Blizzard spells (stuns, illusions, etc.),
making custom spells that need to move special effects and in projectile systems.
Of course one can simply create a unit, use it and remove it when done but its probably
better to only create the unit once and use it as many times as needed instead, which is
what this library tries to do.
Credits:
Vexorian - xe, dummy.mdx (can not possibly enumerate all the other things): http://www.wc3c.net/showthread.php?t=101150
Toadcop - Object Pre-Cacher
Bribe - MissileRecycler
API:
//
// Borrowing/Obtaining a dummy unit:
//
static method get takes nothing returns thistype
// note: facing is in radians not degrees
static method get_facing takes real radians returns thistype
// Obtains a DummyUnit from a native unit.
// In debug mode it prints an error if `u` is not a DummyUnit.
static method from takes unit u returns thistype
//
// Setting the owning player
//
// Note: after borrowing we must call this method to change the owner of the dummy unit
// otherwise the owner will be DUMMY_OWNER (defined in the config section),
// also note that p is an integer, not a player type.
//
method with_owner takes integer p returns thistype
//
// Accessing the wrapped native unit, and setting user data
//
readonly unit u
integer data
//
// Returing a previously borrowed dummy unit
//
// When a dummy unit is returned "home":
// - its owning player is set to DUMMY_OWNER
// - is issued a "stop" order
// - moved to "home" (defined in the config section)
// - its vertex color is set to red: 255, green: 255, blue: 255, alpha: 255
// - its scale is set to 1.0
// - its time scale is set to 1.0
// - it is paused
// - its .data field is set to 0
//
method return_instantly takes nothing returns nothing
// delay is in seconds
method return_after takes real delay returns nothing
//! endnovjass
// These are just shortcuts for importing the dummy.mdx into the map and creating the dummy unit.
// You can do it manually if you want though.
//
// external FileImporter dummy.mdx war3mapImported\dummy.mdx
/*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i setobjecttype("abilities")
//! i createobject("ACrn", "rinc")
//! i makechange(current, "anam", "reincarnation")
//! i makechange(current, "ansf", "")
//! i makechange(current, "aeat", "")
//! i makechange(current, "aart", "ReplaceableTextures\\PassiveButtons\\PASBTNReincarnation.blp")
//
//! i makechange(current, "Ore1", "1", "0")
//! i makechange(current, "acas", "1", "0")
//! i makechange(current, "acdn", "1", "0")
//! i makechange(current, "aeff", "1", "")
//! i makechange(current, "atp1", "1", "")
//! i makechange(current, "aub1", "1", "")
//! i setobjecttype("units")
//! i createobject("ewsp", "e000")
//! i makechange(current, "uabi", "Aloc,Amrf,Avul,rinc")
//! i makechange(current, "uble", "0")
//! i makechange(current, "ucbs", "0")
//! i makechange(current, "uept", "-1")
//! i makechange(current, "uerd", "-1")
//! i makechange(current, "uico", "UI\\Minimap\\MinimapIconCreepLoc2.blp")
//! i makechange(current, "umxp", "-0.01")
//! i makechange(current, "umxr", "-0.01")
//! i makechange(current, "umdl", "war3mapImported\\dummy.mdl")
//! i makechange(current, "uocc", "-1")
//! i makechange(current, "uori", "6")
//! i makechange(current, "uspa", "")
//! i makechange(current, "udty", "normal")
//! i makechange(current, "utar", "none")
//! i makechange(current, "udro", "0")
//! i makechange(current, "umvf", "-10000")
//! i makechange(current, "umvs", "1")
//! i makechange(current, "umas", "1")
//! i makechange(current, "umis", "-10000")
//! i makechange(current, "umvr", "-1")
//! i makechange(current, "umvt", "")
//! i makechange(current, "ucol", "32")
//! i makechange(current, "ufle", "0")
//! i makechange(current, "ufoo", "0")
//! i makechange(current, "uhom", "1")
//! i makechange(current, "uhpm", "1000000000")
//! i makechange(current, "uhpr", "0")
//! i makechange(current, "uhrt", "none")
//! i makechange(current, "ubdg", "1")
//! i makechange(current, "umpi", "1000000000")
//! i makechange(current, "umpm", "1000000000")
//! i makechange(current, "umpr", "100000000")
//! i makechange(current, "usid", "0")
//! i makechange(current, "usin", "0")
//! i makechange(current, "utyp", "standon")
//! i makechange(current, "upgr", "")
//! i makechange(current, "unam", "dummy")
//! i makechange(current, "utip", "")
//! i makechange(current, "utub", "")
//! endexternalblock
*/
globals
private constant integer DUMMY_ID = 'e000'
// adds Amrf, Aloc and Avul to each dummy
private constant boolean ADD_ABILITIES = true
// dummies are created and returned at this position
private constant real DUMMY_HOME_X = -2048.0
private constant real DUMMY_HOME_Y = 2048.0
private location home_loc = Location(DUMMY_HOME_X, DUMMY_HOME_Y)
private constant real DUMMY_HOME_Z = 0.0 - GetLocationZ(home_loc)
// for this player
private constant player DUMMY_PLAYER = Player(15)
// because SetUnitFacing is not exactly instant we use buckets of dummy units roughly
// facing in those directions so instead of SetUnitFacing having to rotate 180 at most
// it would only have to rotate DEGS_PER_BUCKET or less
//
private constant integer BUCKETS_COUNT = 16
private constant real DEGS_PER_BUCKET = 360.0 / BUCKETS_COUNT
endglobals
private struct Timer extends array
readonly static integer max_count = 0
private static Timer head = 0
private Timer next
private timer t
integer data
real timeout
static method start takes integer user_data, real timeout, code callback returns Timer
local Timer this
if head != 0 then
set this = head
set head = head.next
else
set max_count = max_count + 1
set this = max_count
set this.t = CreateTimer()
endif
set this.next = 0
set this.data = user_data
set this.timeout = timeout
call TimerStart(this.t, this, false, null)
call PauseTimer(this.t)
call TimerStart(this.t, timeout, false, callback)
return this
endmethod
method stop takes nothing returns nothing
call TimerStart(this.t, 0.0, false, null)
set this.next = head
set head = this
endmethod
static method get_expired_data takes nothing returns integer
local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
local integer data = t.data
call t.stop()
return data
endmethod
endstruct
static if DEBUG_MODE then
private function panic takes string s returns nothing
call BJDebugMsg("|cffFF0000" + s + "|r")
if 1 / 0 == 1 then
endif
endfunction
endif
struct DummyUnit
readonly unit u
debug private boolean is_dummy
integer data // user data because we use SetUnitUserData for the DummyUnit instance
private integer bucket = -1
private static thistype array buckets // each bucket stores a
private thistype b_next // free list of dummy units
private static method get_internal takes real facing returns thistype
local thistype this = allocate()
if this.u == null then
set this.u = CreateUnit(DUMMY_PLAYER, DUMMY_ID, DUMMY_HOME_X, DUMMY_HOME_Y, facing)
call SetUnitUserData(this.u, this)
debug set this.is_dummy = true
static if ADD_ABILITIES then
// set the "flying flag"
call UnitAddAbility(this.u, 'Amrf')
call UnitRemoveAbility(this.u, 'Amrf')
call UnitAddAbility(this.u, 'Aloc') // unselectable
call UnitAddAbility(this.u, 'Avul') // invulnerable
endif
call SetUnitFlyHeight(u, DUMMY_HOME_Z, 0.0)
endif
call PauseUnit(this.u, false)
call SetUnitPathing(this.u, false)
return this
endmethod
static method get takes nothing returns thistype
return get_internal(0.0)
endmethod
static method get_facing takes real radians returns thistype
local real ang = radians * bj_RADTODEG
local integer b // bucket
local thistype this
// we want ang to be in the range 0.0 .. 359.0
set ang = ang - R2I(ang / 360.0) * 360.0
if ang < 0.0 then
set ang = ang + 360.0
endif
set b = R2I(ang / DEGS_PER_BUCKET)
if buckets[b] != 0 then
set this = buckets[b]
set buckets[b] = buckets[b].b_next
else
set this = get_internal(ang)
endif
set this.bucket = b // doesn't matter as long as it != -1
call SetUnitFacing(this.u, ang)
return this
endmethod
method with_owner takes integer p returns thistype
call SetUnitOwner(this.u, Player(p), /*change-color:*/ true)
return this
endmethod
static method from takes unit u returns thistype
static if DEBUG_MODE then
local thistype this = GetUnitUserData(u)
if not (1 <= this and this <= 8190) or not this.is_dummy then
call panic("unit(" + I2S(GetHandleId(u)) + ") is not a thistype instance")
endif
return this
else
return GetUnitUserData(u)
endif
endmethod
method return_instantly takes nothing returns nothing
local unit u = this.u
local real ang
local integer b // bucket
call SetUnitOwner(u, DUMMY_PLAYER, /*change-color:*/ true)
call IssueImmediateOrder(u, "stop")
call SetUnitX(u, DUMMY_HOME_X)
call SetUnitY(u, DUMMY_HOME_Y)
call SetUnitFlyHeight(u, DUMMY_HOME_Z, 0.0)
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitScale(u, 1.0, 1.0, 1.0)
call SetUnitTimeScale(u, 1.0)
call PauseUnit(u, true)
set this.data = 0
if this.bucket == -1 then
call this.deallocate()
else
set ang = GetUnitFacing(this.u)
set b = R2I(ang / DEGS_PER_BUCKET)
set this.b_next = buckets[b]
set buckets[b] = this
endif
set u = null
endmethod
private static method delayed_return takes nothing returns nothing
call thistype(Timer.get_expired_data()).return_instantly()
endmethod
method return_after takes real delay returns nothing
call Timer.start(this, delay, function thistype.delayed_return)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimedEffect requires Timers, DummyUnit
globals
private location loc = Location(0.0, 0.0)
endglobals
struct TimedEffect
string model
effect ef
real duration
real x
real y
real z
real sc // scale
DummyUnit du
static method create takes string model, real duration returns thistype
local thistype this = allocate()
set this.model = model
set this.duration = duration
set this.sc = 1.0
return this
endmethod
method set_xyz takes real x, real y, real z returns thistype
set this.x = x
set this.y = y
set this.z = z
return this
endmethod
method scale takes real sc returns thistype
set this.sc = sc
return this
endmethod
private static method on_end_duration takes nothing returns nothing
local thistype this = Timer.get_expired_data()
call DestroyEffect(this.ef)
set this.ef = null
call this.du.return_instantly()
call this.destroy()
endmethod
method show takes nothing returns nothing
set this.du = DummyUnit.get()
call SetUnitX(this.du.u, this.x)
call SetUnitY(this.du.u, this.y)
call MoveLocation(loc, this.x, this.y)
call SetUnitFlyHeight(this.du.u, this.z - GetLocationZ(loc), 0.0)
set this.ef = AddSpecialEffectTarget(this.model, this.du.u, "origin")
call SetUnitScale(this.du.u, this.sc, this.sc, this.sc)
call Timer.start(this, this.duration, function thistype.on_end_duration)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library v3 initializer init
static if DEBUG_MODE then
function panic takes string s returns nothing
call BJDebugMsg("|cffFF0000[v3] error: " + s)
if 1 / 0 == 1 then
endif
endfunction
endif
globals
private real vlen = 0.0
public location loc = Location(0.0, 0.0)
public real loc_x = 0.0
public real loc_y = 0.0
public real loc_z = 0.0
private real zl = 0.0
private real zr = 0.0
private real zu = 0.0
private real zd = 0.0
endglobals
globals
private v3 fl_head = 1
private v3 vtmp = 8001
private v3 array next
endglobals
private function init takes nothing returns nothing
local v3 v
// initialize our free list of owned vectors
// owned vectors need to be manually "destroyed"
set v = 1
loop
exitwhen v >= 8000
set next[v] = v3(v + 1)
set v = v + 1
endloop
set next[v3(8000)] = 0
// initialize our circular list of temporary vectors
// temporary vectors are automatically reused, i.e
// they can't be "destroyed"
set v = 8001
loop
exitwhen v >= 8190
set next[v] = v3(v + 1)
set v = v + 1
endloop
set next[v3(8190)] = 8001
// do we really need 190 temporary vectors?
endfunction
struct v3 extends array
real x
real y
real z
method to_owned takes nothing returns v3
local v3 v = fl_head
static if DEBUG_MODE then
if v == 0 then
call panic("cannot allocate a v3 instance|r")
endif
endif
set fl_head = next[fl_head]
set v.x = this.x
set v.y = this.y
set v.z = this.z
return v
endmethod
method to_str takes nothing returns string
return "(" + R2S(this.x) + ", " + R2S(this.y) + ", " + R2S(this.z) + ")"
endmethod
method destroy takes nothing returns nothing
static if DEBUG_MODE then
if this >= 8001 then
call panic("cannot destroy temporary v3(" + I2S(this) + ")")
endif
endif
set next[this] = fl_head
set fl_head = this
endmethod
// can't overload operator=, and set is a keyword
method set_eq_to takes v3 v returns nothing
set this.x = v.x
set this.y = v.y
set this.z = v.z
endmethod
method operator== takes v3 v returns boolean
return this.x == v.x and this.y == v.y and this.z == v.z
endmethod
method len takes nothing returns real
return SquareRoot(this.x * this.x + this.y * this.y + this.z * this.z)
endmethod
method len_sq takes nothing returns real
return this.x * this.x + this.y * this.y + this.z * this.z
endmethod
method len_xy takes nothing returns real
return SquareRoot(this.x * this.x + this.y * this.y)
endmethod
method len_xy_sq takes nothing returns real
return this.x * this.x + this.y * this.y
endmethod
method dot takes v3 v returns real
return this.x * v.x + this.y * v.y + this.z * v.z
endmethod
method ang_rot takes nothing returns real
return Atan2(this.y, this.x)
endmethod
method ang_aoa takes nothing returns real
return Atan2(this.z, SquareRoot(this.x * this.x + this.y * this.y))
endmethod
endstruct
//! textmacro v3_result
local v3 result = vtmp
set vtmp = next[vtmp]
//! endtextmacro
// we would get a "Identifier already in use: v3" error if we call this function v3, i.e
// can't have a struct and a function with the same name :/
//
function v3c takes real x, real y, real z returns v3
//! runtextmacro v3_result()
set result.x = x
set result.y = y
set result.z = z
return result
endfunction
function v3a takes v3 a, v3 b returns v3
//! runtextmacro v3_result()
set result.x = a.x + b.x
set result.y = a.y + b.y
set result.z = a.z + b.z
return result
endfunction
function v3s takes v3 a, v3 b returns v3
//! runtextmacro v3_result()
set result.x = a.x - b.x
set result.y = a.y - b.y
set result.z = a.z - b.z
return result
endfunction
function v3sc takes real s, v3 v returns v3
//! runtextmacro v3_result()
set result.x = s * v.x
set result.y = s * v.y
set result.z = s * v.z
return result
endfunction
function v3sc2 takes v3 v, real s returns v3
//! runtextmacro v3_result()
set result.x = s * v.x
set result.y = s * v.y
set result.z = s * v.z
return result
endfunction
// normalize-or-z
function v3n takes v3 v returns v3
//! runtextmacro v3_result()
set vlen = SquareRoot(v.x * v.x + v.y * v.y + v.z * v.z)
if not (vlen != 0.0) then
set result.x = 0.0
set result.y = 0.0
set result.z = 1.0
else
set vlen = 1.0 / vlen
set result.x = v.x * vlen
set result.y = v.y * vlen
set result.z = v.z * vlen
endif
return result
endfunction
function v3_from_polar takes real angle, real radius returns v3
//! runtextmacro v3_result()
set result.x = radius * Cos(angle)
set result.y = radius * Sin(angle)
set result.z = 0.0
return result
endfunction
function v3_from_spherical takes real rot, real aoa, real radius returns v3
//! runtextmacro v3_result()
set result.x = radius * Cos(rot) * Cos(aoa)
set result.y = radius * Sin(rot) * Cos(aoa)
set result.z = radius * Sin(aoa)
return result
endfunction
function v3_from_unit takes unit u returns v3
//! runtextmacro v3_result()
set result.x = GetUnitX(u)
set result.y = GetUnitY(u)
call MoveLocation(loc, result.x, result.y)
set result.z = GetLocationZ(loc) + GetUnitFlyHeight(u)
return result
endfunction
function v3_from_spell_target takes nothing returns v3
//! runtextmacro v3_result()
set result.x = GetSpellTargetX()
set result.y = GetSpellTargetY()
call MoveLocation(loc, result.x, result.y)
set result.z = GetLocationZ(loc)
return result
endfunction
function v3_terrain_normal takes real x, real y returns v3
//! runtextmacro v3_result()
call MoveLocation(loc, x - 16.0, y)
set zl = GetLocationZ(loc)
call MoveLocation(loc, x + 16.0, y)
set zr = GetLocationZ(loc)
call MoveLocation(loc, x, y + 16.0)
set zu = GetLocationZ(loc)
call MoveLocation(loc, x, y - 16.0)
set zd = GetLocationZ(loc)
set result.x = zl - zr
set result.y = zd - zu
set result.z = 32.0
set vlen = 1.0 / SquareRoot(result.x * result.x + result.y * result.y + 1024.0)
set result.x = result.x * vlen
set result.y = result.y * vlen
set result.z = result.z * vlen
return result
endfunction
function v3_terrain_z takes real x, real y returns real
call MoveLocation(loc, x, y)
return GetLocationZ(loc)
endfunction
function v3_map takes real x, real a1, real b1, real a2, real b2 returns real
local real t = (x - a1) / (b1 - a1)
local real result = (1.0 - t) * a2 + t * b2
return result
endfunction
function v3_map_clamp takes real x, real a1, real b1, real a2, real b2 returns real
local real t = (x - a1) / (b1 - a1)
local real result = (1.0 - t) * a2 + t * b2
if result < a2 then
set result = a2
elseif result > b2 then
set result = b2
endif
return result
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library CineFilter
struct CineFilter
string texture = "ReplaceableTextures\\CameraMasks\\Black_mask.blp"
blendmode blend_mode = BLEND_MODE_BLEND
texmapflags texture_map_flags = TEXMAP_FLAG_NONE
integer a1 = 255
integer r1 = 255
integer g1 = 255
integer b1 = 255
integer a2 = 255
integer r2 = 255
integer g2 = 255
integer b2 = 255
real ux1 = 0.0
real uy1 = 1.0
real vx1 = 0.0
real vy1 = 1.0
real ux2 = 0.0
real uy2 = 1.0
real vx2 = 0.0
real vy2 = 1.0
real display_duration = 0.0
real timer_duration = 0.0
boolean array is_displayed[12]
static method create takes string texture returns thistype
local thistype this = allocate()
set this.texture = texture
return this
endmethod
//
// the default destroy method is sufficient
//
private static integer alpha = 0
private static integer red = 0
private static integer green = 0
private static integer blue = 0
private static method get_argb takes integer color returns nothing
local boolean is_neg = color < 0
local integer t
if is_neg then
set color = color + 0x80000000
endif
set t = color / 0x100
set blue = color - t * 0x100
set color = t
set t = color / 0x100
set green = color - t * 0x100
set color = t
set t = color / 0x100
set red = color - t * 0x100
set color = t
set t = color / 0x100
set alpha = color - t * 0x100
// set color = t
if is_neg then
set alpha = alpha + 0x80
endif
endmethod
method start_argb takes integer argb returns thistype
call get_argb(argb)
set this.a1 = alpha
set this.r1 = red
set this.g1 = green
set this.b1 = blue
return this
endmethod
method end_argb takes integer argb returns thistype
call get_argb(argb)
set this.a2 = alpha
set this.r2 = red
set this.g2 = green
set this.b2 = blue
return this
endmethod
method start_uv takes real ux, real uy, real vx, real vy returns thistype
set this.ux1 = ux
set this.uy1 = uy
set this.vx1 = vx
set this.vy1 = vy
return this
endmethod
method end_uv takes real ux, real uy, real vx, real vy returns thistype
set this.ux2 = ux
set this.uy2 = uy
set this.vx2 = vx
set this.vy2 = vy
return this
endmethod
method duration takes real display_duration, real timer_duration returns thistype
set this.display_duration = display_duration
set this.timer_duration = timer_duration
return this
endmethod
private method show_internal takes nothing returns nothing
call SetCineFilterTexture(this.texture)
call SetCineFilterBlendMode(this.blend_mode)
call SetCineFilterTexMapFlags(this.texture_map_flags)
call SetCineFilterStartUV(this.ux1, this.vx1, this.uy1, this.vy1)
call SetCineFilterEndUV(this.ux2, this.vx2, this.uy2, this.vy2)
call SetCineFilterStartColor(this.r1, this.g1, this.b1, this.a1)
call SetCineFilterEndColor(this.r2, this.g2, this.b2, this.a2)
call SetCineFilterDuration(this.display_duration)
call DisplayCineFilter(true)
endmethod
method show takes nothing returns thistype
local integer i
call this.show_internal()
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
set this.is_displayed[i] = true
set i = i + 1
endloop
return this
endmethod
method hide takes nothing returns thistype
local integer i
call DisplayCineFilter(false)
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
set this.is_displayed[i] = false
set i = i + 1
endloop
return this
endmethod
method show_to takes integer p returns thistype
if GetLocalPlayer() == Player(p) then
call this.show_internal()
endif
set this.is_displayed[p] = true
return this
endmethod
method hide_from takes integer p returns thistype
if GetLocalPlayer() == Player(p) then
call DisplayCineFilter(false)
endif
set this.is_displayed[p] = false
return this
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library misc initializer init requires CineFilter
private function dd takes string s returns nothing
call BJDebugMsg(s)
endfunction
globals
private constant boolean want_play_intro = true
endglobals
private function visibility takes nothing returns nothing
call FogEnable(false)
call FogMaskEnable(false)
call SetTimeOfDay(12)
call SuspendTimeOfDay(true)
endfunction
private function demongo_init takes unit demongo returns nothing
call SetUnitScale(demongo, 0.60, 0.60, 0.60)
call SetUnitVertexColor(demongo, 14, 14, 14, 245)
endfunction
globals
private CineFilter cf_fade_in
private CineFilter cf_fade_out
private CineFilter cf_black_mask
endglobals
private function cine_filter_init takes nothing returns nothing
set cf_black_mask = CineFilter.create("ReplaceableTextures\\CameraMasks\\Black_mask.blp")
call cf_black_mask.start_argb(0xFFFFFFFF)
call cf_black_mask.end_argb(0xFFFFFFFF)
call cf_black_mask.duration(0x7FFFFFFF, 5.0)
set cf_fade_in = CineFilter.create("ReplaceableTextures\\CameraMasks\\Black_mask.blp")
call cf_fade_in.start_argb(0xFFFFFFFF)
call cf_fade_in.end_argb(0x00000000)
call cf_fade_in.duration(5.0, 5.0)
set cf_fade_out = CineFilter.create("ReplaceableTextures\\CameraMasks\\Black_mask.blp")
call cf_fade_out.start_argb(0x00000000)
call cf_fade_out.end_argb(0xFFFFFFFF)
call cf_fade_out.duration(5.0, 5.0)
endfunction
globals
private timer cf_tmr = CreateTimer()
private integer cf_state = 1
endglobals
private function intro_loop takes nothing returns nothing
if cf_state == 1 then
set cf_state = 2
call ShowInterface(false, 0.0)
call cf_black_mask.show()
call TimerStart(cf_tmr, 2.0, false, function intro_loop)
elseif cf_state == 2 then
set cf_state = 3
call dd("[Aku]: From deep within the pit of hate I summon Demongo The Soul Collector who has never failed me.")
call TimerStart(cf_tmr, 3.5, false, function intro_loop)
elseif cf_state == 3 then
set cf_state = 4
call dd("[Demongo]: The more souls I collect the more powerful I become, my masta!")
call TimerStart(cf_tmr, 3.2, false, function intro_loop)
elseif cf_state == 4 then
set cf_state = 5
call dd("[Aku]: Find Samurai Jack and destroy him.")
call TimerStart(cf_tmr, 3.0, false, function intro_loop)
elseif cf_state == 5 then
set cf_state = 6
call dd("[Demongo]: It is as good as done, my masta!")
call TimerStart(cf_tmr, 5.0, false, function intro_loop)
elseif cf_state == 6 then
set cf_state = 7
call ClearTextMessages()
call ShowInterface(true, cf_fade_in.timer_duration)
call cf_black_mask.hide()
call cf_fade_in.show()
call TimerStart(cf_tmr, cf_fade_in.timer_duration, false, function intro_loop)
elseif cf_state == 7 then
call cf_fade_in.hide()
endif
endfunction
private function play_intro takes nothing returns nothing
set cf_state = 1
call TimerStart(cf_tmr, 0.0, false, function intro_loop)
endfunction
private function outro_defeat_loop takes nothing returns nothing
if cf_state == 1 then
set cf_state = 2
call ShowInterface(false, cf_fade_out.timer_duration)
call cf_fade_out.show()
call TimerStart(cf_tmr, cf_fade_out.timer_duration, false, function outro_defeat_loop)
elseif cf_state == 2 then
set cf_state = 3
call cf_fade_out.hide()
call cf_black_mask.show()
call TimerStart(cf_tmr, 1.0, false, function outro_defeat_loop)
elseif cf_state == 3 then
set cf_state = 4
call dd("[Aku]: You have failed me Demongo.")
call TimerStart(cf_tmr, 3.0, false, function outro_defeat_loop)
elseif cf_state == 4 then
set cf_state = 5
call dd("[Demongo]: No masta please forgive me.")
call TimerStart(cf_tmr, 3.0, false, function outro_defeat_loop)
elseif cf_state == 5 then
set cf_state = 6
call dd("[Aku]: Hm...")
call TimerStart(cf_tmr, 2.0, false, function outro_defeat_loop)
elseif cf_state == 6 then
set cf_state = 7
call dd("[Aku]: No. (crushing sounds, Aku crushed Demongo)")
call TimerStart(cf_tmr, 2.0, false, function outro_defeat_loop)
elseif cf_state == 7 then
call GameOverDialogBJ(Player(0), false)
endif
endfunction
private function play_outro_defeat takes nothing returns nothing
set cf_state = 1
call TimerStart(cf_tmr, 0.0, false, function outro_defeat_loop)
endfunction
private function outro_victory_loop takes nothing returns nothing
if cf_state == 1 then
set cf_state = 2
call ShowInterface(false, cf_fade_out.timer_duration)
call cf_fade_out.show()
call TimerStart(cf_tmr, cf_fade_out.timer_duration, false, function outro_victory_loop)
elseif cf_state == 2 then
set cf_state = 3
call cf_fade_out.hide()
call cf_black_mask.show()
call TimerStart(cf_tmr, 1.0, false, function outro_victory_loop)
elseif cf_state == 3 then
set cf_state = 4
call dd("[Aku]: Blahahahaha. Ahahaha.")
call TimerStart(cf_tmr, 3.0, false, function outro_victory_loop)
elseif cf_state == 4 then
set cf_state = 5
call dd("[Aku]: You are truly my most powerful minion Demongo.")
call TimerStart(cf_tmr, 3.0, false, function outro_victory_loop)
elseif cf_state == 5 then
set cf_state = 6
call dd("[Demongo] The future is Aku, masta!")
call TimerStart(cf_tmr, 3.0, false, function outro_victory_loop)
elseif cf_state == 6 then
call MeleeVictoryDialogBJ(Player(0), false)
endif
endfunction
private function play_outro_victory takes nothing returns nothing
set cf_state = 1
call TimerStart(cf_tmr, 0.0, false, function outro_victory_loop)
endfunction
private function on_unit_death takes nothing returns nothing
local unit dying_unit = GetDyingUnit()
if dying_unit == gg_unit_N000_0002 then
call play_outro_defeat()
elseif dying_unit == gg_unit_O000_0008 then
call play_outro_victory()
endif
endfunction
private function init takes nothing returns nothing
local trigger t
call visibility()
call demongo_init(gg_unit_N000_0002)
call cine_filter_init()
if want_play_intro then
call play_intro()
endif
set t = CreateTrigger()
call TriggerRegisterUnitEvent(t, gg_unit_N000_0002, EVENT_UNIT_DEATH)
call TriggerRegisterUnitEvent(t, gg_unit_O000_0008, EVENT_UNIT_DEATH)
call TriggerAddAction(t, function on_unit_death)
endfunction
endlibrary