library withmanacharge requires /*
*/Ht, /* http://www.hiveworkshop.com/threads/t-real-x-vs-t-x-real.288833/
*/ updatelist, /* http://www.hiveworkshop.com/threads/fire-and-ice-system.290464/#post-3120100
*/ v3 /* in the map */
globals
private constant integer MANA_CHARGE = 'A002'
private constant integer MANA_CHARGE_ON = 852589
private constant integer MANA_CHARGE_OFF = 852590
private constant string MANA_CHARGE_HK = "X"
private constant integer MANA_DISCHARGE = 'A001'
private constant real MIN_REQUIRED_MANA = 50.0
private constant real MAX_MANA_CHARGE = 100.0
private constant real MANA_CHARGE_TEXT_SIZE = 9.0 * 0.0023
// if the mana-discharge is casted on a point within the circle with a center point
// the position of the caster and with `this` radius the mana-discharge will act
// as a propeller for the caster and would move them in the opposite direction,
// otherwise the mana-discharge acts as an instant linear projectile with a limited range
private constant real DISCHARGE_GROUND_RADIUS = 384.0
private constant real GRAVITY = -50.0
private constant real DT = 1.0 / 32.0
// UnitDamageTarget arguments
private constant boolean MELEE_ATTACK = false
private constant boolean RANGE_ATTACK = true
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // spell
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL // ignore armor value
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// it takes `this` many seconds to charge up to `max-mana-charge`
private function max_mana_charge_time_for_level takes integer level returns real
return 5.0 - (level - 1)
endfunction
// the caster can only hold the max-mana-charge for `this` many seconds
// after that the charge would be discharged onto the caster
private function max_mana_charge_hold_time_for_level takes integer level returns real
return 10.0 + 2.0 * (level - 1)
endfunction
private function charge_to_velocity takes real charge returns real
return 15.0 * charge
endfunction
private function charge_to_self_damage takes real charge, integer level returns real
return (2.6667 + 0.0 * level) * charge
endfunction
private function charge_to_damage takes real charge, integer level returns real
return (1.0 + level) * charge
endfunction
private function max_hit_distance_for_level takes integer level returns real
return 800.0 + (level - 1) * 100.0
endfunction
private keyword Mana_Charge
native UnitAlive takes unit u returns boolean
private function targets_allowed takes Mana_Charge mc, unit u, real z_hit returns boolean
local real uz
local real z_diff
if not UnitAlive(u) then
return false
endif
if not IsUnitEnemy(u, mc.owner) then
return false
endif
set v3_loc_x = GetUnitX(u)
set v3_loc_y = GetUnitY(u)
call MoveLocation(v3_loc, v3_loc_x, v3_loc_y)
set uz = GetLocationZ(v3_loc) + GetUnitFlyHeight(u)
set z_diff = uz - z_hit
if z_diff < 0.0 then
set z_diff = -z_diff
endif
if z_diff > 128.0 then
return false
endif
return true
endfunction
globals
private effect effect_result
endglobals
private function AddSpecialEffectZ takes string effec_path, real x, real y, real z returns effect
local destructable platform = CreateDestructableZ('OTip', x, y, z, 0, 1, 0)
set effect_result = AddSpecialEffect(effec_path, x, y)
call RemoveDestructable(platform)
set platform = null
return effect_result
endfunction
private struct Mana_Charge
static real ul_timer_frequency = DT
static code ul_update_handler
implement updatelist
static real map_min_x
static real map_max_x
static real map_min_y
static real map_max_y
static Ht ht
static group G = CreateGroup()
static constant integer STATE_WAIT_TURN_OFF = 0
static constant integer STATE_TURN_OFF = 1
static constant integer STATE_CHARGING = 2
static constant integer STATE_MAX_CHARGED = 3
static constant integer STATE_DISCHARGING_GROUND = 4
static constant integer STATE_LIGHTNING_FADE = 5
integer state
unit caster
player owner
integer level
// turn-off
integer turn_off_delay_ticks
// charging
real mana_dec
real mana_charge
texttag tt
// max-charged
integer charge_hold_ticks
// discharging-ground
v3 p
v3 tp
v3 dp
v3 ddp
real dp_rot
// lightning-fade
integer li_max_ticks
integer li_ticks
lightning li
effect li_effect
private static code on_event_handler
private static method onInit takes nothing returns nothing
local trigger t
local rect r = GetPlayableMapRect()
set map_min_x = GetRectMinX(r) + 64.0
set map_max_x = GetRectMaxX(r) - 64.0
set map_min_y = GetRectMinY(r) + 64.0
set map_max_y = GetRectMaxY(r) - 64.0
set ht = Ht.create()
call ExecuteFunc("s__" + "thistype" + "_set_handlers")
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, on_event_handler)
endmethod
method mana_charge_off takes nothing returns nothing
// selecting a unit and calling ForceUIKey doesn't happen instantly/synchronously
// so we transition to this wait/do-nothing state until the turn-off order is issued
set this.state = STATE_WAIT_TURN_OFF
if this.owner == GetLocalPlayer() then
call SelectUnit(this.caster, true)
// issue MANA_CHARGE_OFF order without interrupting the caster's current order
call ForceUIKey(MANA_CHARGE_HK)
endif
endmethod
method mana_charge_tt_update_pos takes nothing returns nothing
// call SetTextTagPosUnit(this.tt, this.caster, /*height-offset:*/ 32.0)
call SetTextTagPos(this.tt, GetUnitX(this.caster) - 24.0, GetUnitY(this.caster), /*height-offset:*/ 144.0)
endmethod
method destroy takes nothing returns nothing
call ht[ht.h2i(this.caster)].int_d()
set this.caster = null
set this.owner = null
call this.ul_remove()
call this.deallocate()
endmethod
private static method on_event takes nothing returns nothing
local eventid ee = GetTriggerEventId()
local integer o
local real lightning_fade_time = 0.5
local real charge
local real velocity
local real dp_aoa
local real dist_xy
local real max_hit_distance
local real hit_radius = 128.0
local thistype this
local v3 tpp
local unit u
local v3 curr_p
local v3 step
local integer i
local v3 n
local v3 a
local v3 up
local v3 ua
local v3 ul
local real z
if ee == EVENT_PLAYER_UNIT_ISSUED_ORDER then
set o = GetIssuedOrderId()
if o == MANA_CHARGE_ON then
set this = allocate()
set this.caster = GetTriggerUnit()
set this.owner = GetOwningPlayer(this.caster)
if GetUnitState(this.caster, UNIT_STATE_MANA) < MIN_REQUIRED_MANA then
// ForceUIKey needs a bit of delay
set this.state = STATE_TURN_OFF
set this.turn_off_delay_ticks = R2I(0.1 / DT)
else
set this.state = STATE_CHARGING
set this.level = GetUnitAbilityLevel(this.caster, MANA_CHARGE)
set this.mana_dec = (MAX_MANA_CHARGE / max_mana_charge_time_for_level(this.level)) * DT
set this.mana_charge = 0.0
set this.tt = CreateTextTag()
call SetTextTagText(this.tt, "0 mc", MANA_CHARGE_TEXT_SIZE)
call this.mana_charge_tt_update_pos()
call SetTextTagColor(this.tt, /*R:*/ 0x0E, /*G:*/ 0x2F, /*B:*/ 0xA7, /*A:*/ 0xFF)
call SetTextTagVisibility(this.tt, IsPlayerAlly(this.owner, GetLocalPlayer()))
call SetTextTagPermanent(this.tt, true)
call UnitAddAbility(this.caster, MANA_DISCHARGE)
endif
set ht.select()[ht.h2i(this.caster)].int = this
call this.ul_add()
elseif o == MANA_CHARGE_OFF then
set this = ht.select()[ht.h2i(GetTriggerUnit())].int
call DestroyTextTag(this.tt)
set this.tt = null
call UnitRemoveAbility(this.caster, MANA_DISCHARGE)
call this.destroy()
endif
elseif ee == EVENT_PLAYER_UNIT_SPELL_EFFECT then
if GetSpellAbilityId() != MANA_DISCHARGE then
return
endif
set this = ht.select()[ht.h2i(GetTriggerUnit())].int
call DestroyTextTag(this.tt)
set this.tt = null
set this.p = v3_from_unit(this.caster)
set this.tp = v3_from_spell_target()
set tpp = v3s(this.p, this.tp)
set dist_xy = tpp.len_xy()
if dist_xy <= DISCHARGE_GROUND_RADIUS then
set this.state = STATE_DISCHARGING_GROUND
set this.p = this.p.to_owned()
set this.tp = this.tp.to_owned()
set this.dp_rot = tpp.ang_rot()
set dp_aoa = v3_map(dist_xy, DISCHARGE_GROUND_RADIUS, 0.0, 60.0, 90.0)
set charge = v3_map(this.mana_charge, 0.0, MAX_MANA_CHARGE, 0.0, 100.0)
set velocity = charge_to_velocity(charge)
set this.dp = v3_from_spherical(this.dp_rot, bj_DEGTORAD * dp_aoa, velocity * DT).to_owned()
set this.ddp = v3sc(DT, v3c(0.0, 0.0, GRAVITY)).to_owned()
set this.li = AddLightningEx("DRAM", false, this.p.x, this.p.y, this.p.z, tp.x, tp.y, tp.z)
set this.li_max_ticks = R2I(lightning_fade_time / DT)
set this.li_ticks = this.li_max_ticks
set this.dp_rot = bj_RADTODEG * this.dp_rot
call SetUnitFacing(this.caster, this.dp_rot)
call SetUnitPropWindow(this.caster, 0.0) // disable caster movement
call UnitAddAbility(this.caster, 'Amrf')
call UnitRemoveAbility(this.caster, 'Amrf')
else
set this.state = STATE_LIGHTNING_FADE
set this.li_max_ticks = R2I(lightning_fade_time / DT)
set this.li_ticks = this.li_max_ticks
set max_hit_distance = max_hit_distance_for_level(this.level)
set i = R2I(max_hit_distance / hit_radius + 0.5)
set curr_p = v3c(this.p.x, this.p.y, 0.0)
set step = v3_from_polar(tpp.ang_rot() + bj_PI, hit_radius)
set z = this.p.z + 64.0
loop
exitwhen i <= 0
call GroupEnumUnitsInRange(G, curr_p.x, curr_p.y, hit_radius, null)
loop
set u = FirstOfGroup(G)
exitwhen u == null
call GroupRemoveUnit(G, u)
if targets_allowed(this, u, z) then
exitwhen true
endif
endloop
if u != null then
call GroupClear(G)
exitwhen true
endif
call curr_p.set_eq(v3a(curr_p, step))
set i = i - 1
endloop
if u != null then
set n = v3n(step)
set n.z = 0.0
set a = v3c(this.p.x, this.p.y, 0.0)
set up = v3c(GetUnitX(u), GetUnitY(u), 0.0)
set ua = v3s(a, up)
set ul = v3a(up, v3s(ua, v3sc(ua.dot(n), n)))
set this.li_effect = AddSpecialEffectZ("Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl", ul.x, ul.y, z)
set this.li = AddLightningEx("DRAM", false, this.p.x, this.p.y, z, ul.x, ul.y, z)
call UnitDamageTarget(this.caster, u, charge_to_damage(this.mana_charge, this.level), MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
set u = null
else
set this.li = AddLightningEx("DRAM", false, this.p.x, this.p.y, z, curr_p.x, curr_p.y, z)
endif
endif
// it would be better if we could disable/silence the mana charge ability while
// the caster is discharging on the ground instead of removing/hiding it
call UnitRemoveAbility(this.caster, MANA_CHARGE)
// the mana discharge ability can only be used once after the mana charge
// is successfully (having enough mana) turned on, again it would be nicer to
// be able to disable/silence it instead of removing/hiding it
// note: this needs to be called after GetSpellTargetX|Y otherwise we get 0.0
call UnitRemoveAbility(this.caster, MANA_DISCHARGE)
endif
endmethod
private static method ul_update takes nothing returns nothing
local thistype this
local real mana
local integer i
set i = 1
loop
exitwhen i > ul_count
set this = ul_list[i]
if this.state == STATE_WAIT_TURN_OFF then
// do nothing while mana charge is turning off
elseif this.state == STATE_TURN_OFF then
set this.turn_off_delay_ticks = this.turn_off_delay_ticks - 1
if this.turn_off_delay_ticks <= 0 then
call this.mana_charge_off()
endif
elseif this.state == STATE_CHARGING then
set this.mana_charge = this.mana_charge + this.mana_dec
set mana = GetUnitState(this.caster, UNIT_STATE_MANA) - this.mana_dec
call SetUnitState(this.caster, UNIT_STATE_MANA, mana)
if mana < 1.0 or this.mana_charge > MAX_MANA_CHARGE then
set this.state = STATE_MAX_CHARGED
set this.charge_hold_ticks = R2I(max_mana_charge_hold_time_for_level(this.level) / DT)
if this.mana_charge > MAX_MANA_CHARGE then
set this.mana_charge = MAX_MANA_CHARGE
endif
endif
call SetTextTagText(this.tt, I2S(R2I(this.mana_charge)) + " mc", MANA_CHARGE_TEXT_SIZE)
call this.mana_charge_tt_update_pos()
elseif this.state == STATE_MAX_CHARGED then
call this.mana_charge_tt_update_pos()
set this.charge_hold_ticks = charge_hold_ticks - 1
if this.charge_hold_ticks == 0 then
call DestroyEffect(AddSpecialEffect("Units\\NightElf\\Wisp\\WispExplode.mdl", GetUnitX(this.caster), GetUnitY(this.caster)))
call UnitDamageTarget(this.caster, this.caster, charge_to_self_damage(this.mana_charge, this.level), MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call this.mana_charge_off()
endif
elseif this.state == STATE_DISCHARGING_GROUND then
call this.dp.set_eq(v3a(this.dp, this.ddp))
call this.p.set_eq(v3a(this.p, this.dp))
if this.p.x < map_min_x then
set this.p.x = map_min_x
elseif this.p.x > map_max_x then
set this.p.x = map_max_x
endif
if this.p.y < map_min_y then
set this.p.y = map_min_y
elseif this.p.y > map_max_y then
set this.p.y = map_max_y
endif
call SetUnitX(this.caster, this.p.x)
call SetUnitY(this.caster, this.p.y)
call MoveLocation(v3_loc, this.p.x, this.p.y)
set v3_loc_z = GetLocationZ(v3_loc)
call SetUnitFlyHeight(this.caster, this.p.z - v3_loc_z, 0.0)
call SetUnitFacing(this.caster, this.dp_rot)
call MoveLightningEx(this.li, false, this.tp.x, this.tp.y, this.tp.z, this.p.x, this.p.y, this.p.z)
if this.p.z < v3_loc_z then
set this.state = STATE_LIGHTNING_FADE
call this.p.destroy()
call this.tp.destroy()
call this.dp.destroy()
call this.ddp.destroy()
call SetUnitFlyHeight(this.caster, 0.0, 0.0)
call SetUnitPropWindow(this.caster, bj_DEGTORAD * GetUnitDefaultPropWindow(this.caster))
endif
elseif this.state == STATE_LIGHTNING_FADE then
set this.li_ticks = this.li_ticks - 1
if this.li_ticks <= 0 then
call DestroyLightning(this.li)
set this.li = null
if this.li_effect != null then
call DestroyEffect(this.li_effect)
set this.li_effect = null
endif
call UnitAddAbility(this.caster, MANA_CHARGE)
call SetUnitAbilityLevel(this.caster, MANA_CHARGE, this.level)
call this.destroy()
set i = i - 1
else
call SetLightningColor(this.li, 1.0, 1.0, 1.0, I2R(this.li_ticks) / this.li_max_ticks)
endif
endif
set i = i + 1
endloop
endmethod
private static method set_handlers takes nothing returns nothing
set on_event_handler = function thistype.on_event
set ul_update_handler = function thistype.ul_update
endmethod
endstruct
endlibrary