//! zinc
/*
-------------------------------------------------------------------------------------------
Rain of Arrows v1.01 by scorpion182
requires:
[-X-] TimerUtils, SimError, BoundSentinel, xe by vexorian
[-X-] ParabolicMovement by Moyack & Spec
-------------------------------------------------------------------------------------------
*/
library RainofArrows requires TimerUtils, ParabolicMovement, SimError, xefx, xedamage, optional BoundSentinel
{
/*
-------------------------------------------------------------------------------------------
CALIBRATION SECTION
-------------------------------------------------------------------------------------------
*/
constant integer SPELL_ID = 'A000'; /*spell id*/
constant string ARROW_PATH = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"; /*the arrow effect path*/
constant string FLASH_PATH = "Abilities\\Weapons\\FragDriller\\FragDriller.mdl"; /*the arrow flash effect path*/
constant string DAMAGE_FX = "Abilities\\Weapons\\Rifle\\RifleImpact.mdl"; /*on-damage effect*/
constant string DAMAGE_ATCH = "origin"; /*on-damage effect attachment point*/
constant integer ARROW_SCALE = 1; /*arrow scale*/
constant real MAX_H = 500.; /*maximum parabola height*/
constant real SPEED = 800. * XE_ANIMATION_PERIOD; /*missile speed*/
constant real MIN_DIST = 300.; /*minimum casting distance, the parabolic movement looks weird if the distance is too short*/
constant real MIN_H = 80.; /*minimum distance for the rain arrows, must higher than 80., otherwise it won't work*/
constant string ERROR_MSG = "Inside Minimum Range." ; /*display error message*/
constant boolean PRELOAD_FX = true; /*preload effect?*/
function GetAoE(integer lvl)->real
{
return 400. + lvl * 0; /*spell aoe, must match the object editor field*/
}
function GetDamage(integer lvl)->real
{
return 25. * lvl; /*deals damage each arrow*/
}
function GetCollisionSize(integer lvl)->real
{
return 125. + lvl * 0; /*collision size each arrow*/
}
function GetArrowCount(integer lvl)->integer
{
return 12 + lvl * 0; /*how many arrows*/
}
/* damage filter */
function DamageOptions(xedamage spellDamage)
{
spellDamage.dtype = DAMAGE_TYPE_UNIVERSAL;
spellDamage.atype = ATTACK_TYPE_NORMAL;
spellDamage.exception=UNIT_TYPE_STRUCTURE; /*doesn't damage building*/
spellDamage.visibleOnly = true;
spellDamage.damageAllies = false; /* damage allies if true */
spellDamage.damageTrees = true; /* damage destructables? */
spellDamage.useSpecialEffect(DAMAGE_FX, DAMAGE_ATCH);
}
/*
-------------------------------------------------------------------------------------------
END OF CALIBRATION SECTION
-------------------------------------------------------------------------------------------
*/
constant real A = 2 * bj_PI;
location l = Location(0, 0);
xedamage xed;
function GetPointZ(real x, real y)->real
{
MoveLocation(l, x, y);
return GetLocationZ(l);
}
/*rain of arrows*/
struct rain
{
unit caster; /*the caster*/
timer t;
real tx; /*spell target x*/
real ty; /*spell target y*/
xefx fx;
integer lvl; /*ability level*/
real zpoint; /*spell target z*/
static method create(unit c, real x, real y, real tx, real ty, integer lvl)->thistype
{
thistype this = thistype.allocate();
real dx = tx - x;
real dy = ty - y;
real angle = Atan2(dy, dx);
this.caster = c;
this.t = NewTimer();
this.tx = tx;
this.ty = ty;
this.lvl = lvl;
this.fx = xefx.create(x, y, angle);
this.fx.fxpath = ARROW_PATH;
this.fx.scale = ARROW_SCALE;
this.fx.z = MAX_H;
this.zpoint = GetPointZ(tx, ty);
SetTimerData(this.t, this);
TimerStart(this.t, XE_ANIMATION_PERIOD, true, function thistype.onMove);
return this;
}
static method onMove()
{
thistype this = thistype(GetTimerData(GetExpiredTimer()));
real x = this.tx - this.fx.x;
real y = this.ty - this.fx.y;
real z = -this.fx.z + SPEED;
real distance = SquareRoot(x*x + y*y + z*z);
real angle1 = Atan2(y, x);
real angle2 = Acos(z / distance);
real angle3 = Atan2(z, SquareRoot(x*x + y*y));
if (distance > MIN_H && this.fx.z + this.zpoint > this.zpoint + MIN_H)
{
this.fx.xyangle = angle1;
this.fx.x += SPEED * Cos(angle1) * Sin(angle2);
this.fx.y += SPEED * Sin(angle1) * Sin(angle2);
this.fx.z += SPEED * Cos(angle2);
this.fx.zangle = angle3;
}
else
{
this.destroy();
}
}
method onDestroy()
{
xed.damageAOE(this.caster,this.fx.x, this.fx.y, GetCollisionSize(this.lvl), GetDamage(this.lvl));
this.fx.flash(FLASH_PATH);
ReleaseTimer(this.t);
this.fx.destroy();
}
}
constant real factor = 2.;
/*parabolic arrow*/
struct data
{
unit caster; /*the caster*/
timer t;
real pos; /*current pos*/
real dist; /*total distance*/
integer lvl; /*ability level*/
xefx fx;
real tx; /*spell target x*/
real ty; /*spell target y*/
static method create(unit c, real tx, real ty)->thistype
{
thistype this = thistype.allocate();
real x = GetUnitX(c);
real y = GetUnitY(c);
real dx = tx - x;
real dy = ty - y;
real angle = Atan2(dy, dx);
this.caster = c;
this.t = NewTimer();
this.lvl = GetUnitAbilityLevel(c, SPELL_ID);
this.tx = tx;
this.ty = ty;
this.pos = SquareRoot(dx * dx + dy * dy)*factor;
this.dist = this.pos;
this.fx = xefx.create(x, y, angle);
this.fx.fxpath = ARROW_PATH;
this.fx.scale = ARROW_SCALE;
return this;
}
method onDestroy()
{
integer i = 0;
real x = this.fx.x;
real y = this.fx.y;
real angle;
real dis;
real tx;
real ty;
rain s;
this.fx.flash(FLASH_PATH);
ReleaseTimer(this.t);
this.fx.destroy();
/*create rain arrows*/
for(i = 0; i<GetArrowCount(this.lvl); i += 1)
{
angle = GetRandomReal(0, A);
dis = GetRandomReal(1, GetAoE(this.lvl));
tx = x + dis * Cos (angle);
ty = y + dis * Sin (angle);
s = s.create(this.caster, x, y, tx, ty, this.lvl);
}
}
/*move the parabolic arrow*/
static method onLoop()
{
thistype this = thistype (GetTimerData(GetExpiredTimer()));
real x = this.fx.x;
real y = this.fx.y;
real z = this.fx.z;
real dx;
real dy;
real dz;
if (this.pos>this.dist/factor)
{
this.fx.x += SPEED * Cos(this.fx.xyangle);
this.fx.y += SPEED * Sin(this.fx.xyangle);
this.fx.z = ParabolaZ(MAX_H, this.dist, this.pos);
dx = this.fx.x - x;
dy = this.fx.y - y;
dz = this.fx.z - z;
this.fx.zangle = Atan(dz * dz / (dx * dx + dy * dy) );
this.pos -= SPEED;
}
else
{
this.destroy();
}
}
static method SpellEffect()->boolean
{
thistype this;
if (GetSpellAbilityId() == SPELL_ID)
{
this = thistype.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY());
SetTimerData(this.t, this);
TimerStart(this.t, XE_ANIMATION_PERIOD, true, function thistype.onLoop);
}
return false;
}
/*check minimum casting distance*/
static method CheckMinDistance()->boolean
{
unit u = GetTriggerUnit();
real x = GetUnitX(u);
real y = GetUnitY(u);
real dx = x - GetSpellTargetX();
real dy = y - GetSpellTargetY();
if (GetSpellAbilityId() == SPELL_ID && dx * dx + dy * dy<MIN_DIST * MIN_DIST)
{
SimError(GetOwningPlayer(u), ERROR_MSG);
PauseUnit(u, true);
IssueImmediateOrder(u, "stop");
PauseUnit(u, false);
}
u = null;
return false;
}
static method onInit()
{
trigger t = CreateTrigger();
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddCondition(t, Condition(function thistype.SpellEffect));
t = CreateTrigger();
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CHANNEL);
TriggerAddCondition(t, Condition(function thistype.CheckMinDistance));
/* init xedamage */
xed = xedamage.create();
DamageOptions(xed);
/*preload effect*/
static if (PRELOAD)
{
Preload(ARROW_PATH);
Preload(FLASH_PATH);
Preload(DAMAGE_FX);
}
}
}
}
//! endzinc