Name | Type | is_array | initial_value |
Example | unit | No | |
GoldAndLumber | real | No |
//TESH.scrollpos=120
//TESH.alwaysfold=0
//! zinc
library Init
{
public unit Hero[];
public real X[], Y[];
public real face[];
public integer N = 0;
private struct ReviveData {
public integer ID;
private integer UnitType;
private integer UnitData;
private real Delay;
public static method create(unit target, real delay) -> thistype {
thistype this = thistype.allocate();
this.UnitType = GetUnitTypeId(target);
this.UnitData = GetUnitUserData(target);
this.Delay = delay;
ReviveManager.Add(this);
return this;
}
public method Update() {
unit tmp;
this.Delay -= ReviveManager.UpdateRate;
if (this.Delay <= 0) {
tmp = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), this.UnitType, X[this.UnitData], Y[this.UnitData], face[this.UnitData]);
SetUnitUserData(tmp, this.UnitData);
tmp = null;
ReviveManager.Remove(this);
this.destroy();
}
}
}
private struct ReviveManager {
public static real UpdateRate;
private static timer Timer;
private static ReviveData List[];
private static integer Count;
public static method Add(ReviveData entity) {
integer index = Count;
List[index] = entity;
entity.ID = index;
Count += 1;
if (index == 0) {
TimerStart(Timer, thistype.UpdateRate, true, function thistype.Update);
}
}
public static method Remove(ReviveData entity) {
if (entity.ID >= 0 && entity.ID < Count) {
Count -= 1;
List[entity.ID] = List[Count];
List[entity.ID].ID = entity.ID;
entity.ID = -1;
if (Count == 0) {
PauseTimer(Timer);
}
}
}
private static method onInit() {
UpdateRate = 0.1;
Timer = CreateTimer();
Count = 0;
}
private static method Update() {
integer i;
for (i = 0; i < Count; i += 1) {
List[i].Update();
}
}
}
function onInit() {
trigger t = CreateTrigger();
trigger t2 = CreateTrigger();
trigger t3 = CreateTrigger();
real startX = GetStartLocationX(0);
real startY = GetStartLocationY(0);
integer i;
Hero[0] = CreateUnit(Player(0), 'Hblm', startX, startY - 100.0, 270.0);
Hero[1] = CreateUnit(Player(0), 'Hblm', startX - 100, startY + 50.0, 270.0);
Hero[2] = CreateUnit(Player(0), 'Hblm', startX + 100, startY + 50.0, 270.0);
for (i = 0; i < 3; i += 1) {
SetHeroLevel(Hero[i], 10, true);
UnitAddAbility(Hero[i], 'A001');
}
//: ==----
// Restore Heroes
//: ==----
TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC);
TriggerAddAction(t, function() {
integer i;
for (i = 0; i < 3; i += 1) {
SetUnitState(Hero[i], UNIT_STATE_LIFE, GetUnitState(Hero[i], UNIT_STATE_MAX_LIFE));
SetUnitState(Hero[i], UNIT_STATE_MANA, GetUnitState(Hero[i], UNIT_STATE_MAX_MANA));
SetHeroLevel(Hero[i], GetHeroLevel(Hero[i]) + 1, true);
UnitResetCooldown(Hero[i]);
}
});
TriggerRegisterPlayerUnitEvent(t3, Player(0), EVENT_PLAYER_UNIT_DEATH, null);
TriggerAddAction(t3, function() {
unit u = GetTriggerUnit();
if (IsUnit(u, Hero[0]) || IsUnit(u, Hero[1]) || IsUnit(u, Hero[2])) {
ReviveHero(u, GetUnitX(u), GetUnitY(u), true);
}
u = null;
});
//: ==----
// Respawn System
//: ==----
GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, Player(PLAYER_NEUTRAL_AGGRESSIVE), function() -> boolean {
X[N] = GetUnitX(GetFilterUnit());
Y[N] = GetUnitY(GetFilterUnit());
face[N] = GetUnitFacing(GetFilterUnit());
SetUnitUserData(GetFilterUnit(), N);
N += 1;
return false;
});
TriggerRegisterPlayerUnitEvent(t2, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_DEATH, null);
TriggerAddAction(t2, function() {
ReviveData.create(GetTriggerUnit(), 20.0);
});
//: ==----
// Visibility
//: ==----
bj_lastCreatedFogModifier = CreateFogModifierRect(Player(0), FOG_OF_WAR_VISIBLE, bj_mapInitialPlayableArea, true, false);
FogModifierStart(bj_lastCreatedFogModifier);
//: ==----
// Game messages
//: ==----
DisplayTimedTextToPlayer(Player(0), 0., 0., 6., "Blaze Guardian by Wilzirius");
DisplayTimedTextToPlayer(Player(0), 0., 0., 6., " ");
DisplayTimedTextToPlayer(Player(0), 0., 0., 6., "Units respawn automatically in their initial positions");
DisplayTimedTextToPlayer(Player(0), 0., 0., 6., " ");
DisplayTimedTextToPlayer(Player(0), 0., 0., 6., "Press Esc to restore your hero and level up.");
SelectUnit(Hero[0], true);
t = null;
t2 = null;
t3 = null;
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! zinc
library BlazeGuardianConfig {
// The rate at which the internal timer updates everything. Currently it's 32 ticks per second.
public constant real TIMER_UPDATE_RATE = 0.031250000;
// =========================================================
// --------------- Spell Core Configuration ----------------
// =========================================================
// The ID of the spell Blaze Guardian in the ability editor.
public constant integer SPELL_ID = 'A001';
// The area of effect of the shield.
// Increases by level and determines the size of the shield made of nodes.
public function SpellAreaOfEffectByLevel(integer spellLevel) -> real {
return 450.0 + 50.0 * I2R(spellLevel);
}
// The amount of damage dealt to units that are knockbacked when the caster is still charging up the shield.
// It happens before the shield nodes are created and connected.
public function SpellKnockbackDamageByLevel(integer spellLevel) -> real {
return 25.0 * spellLevel;
}
// The amount of damage dealt to units who touch the shield.
public function SpellShieldDamageByLevel(integer spellLevel) -> real {
return 50.0 * spellLevel;
}
// The duration of the shield only. It doesn't take into account the charging duration.
public function SpellDurationByLevel(integer spellLevel) -> real {
return 4.0 + spellLevel;
}
// The animation played when the caster is casting the spell.
public constant string SPELL_CASTING_ANIMATION = "Spell Channel";
// The amount of time between the creation of special effects, from the beginning of the spell
// until the shield disappears.
public constant real SPELL_CASTING_SFX_INTERVAL = 0.25;
// The special effects played during the whole spell, at the position of the caster.
public constant string SPELL_CASTING_SFX1 = "Abilities\\Weapons\\DemolisherFireMissile\\DemolisherFireMissile.mdl";
public constant string SPELL_CASTING_SFX2 = "Flame Burst.mdl";
// Determines who long the charging phase lasts.
// The charging phase happens after the spell is cast and before the fire waves.
public constant real SPELL_CHARGE_DURATION = 1.0;
// Determines the Attack Type when dealing damage to units.
// Considering the game constants haven't been changed, ATTACK_TYPE_CHAOS will ensure
// the determined amount of damage is dealt in total, ignoring the target defense rating.
public constant attacktype SPELL_ATTACK_TYPE = ATTACK_TYPE_CHAOS;
// Determines the Damage Type when dealing damage to units.
public constant damagetype SPELL_DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL;
// =========================================================
// ---------------- Fire Wave Configuration ----------------
// =========================================================
// The number of fire waves played before the node shield is created.
public constant integer FIRE_WAVE_COUNT = 2;
// The amount of time between each fire wave.
public constant real FIRE_WAVE_DELAY = 0.8;
// The model of the projectiles that make up the fire waves.
public constant string FIRE_WAVE_PROJECTILE_SFX = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl";
// The scale size of the projectiles. In this case, it means they will be 150% greater in size.
public constant real FIRE_WAVE_PROJECTILE_SCALE = 1.5;
// The number of projectiles on each fire wave.
public constant integer FIRE_WAVE_PROJECTILE_AMOUNT = 18;
// The movement speed of each projectile, in Wc3 units per second.
public constant real FIRE_WAVE_PROJECTILE_SPEED = 1200.0 * TIMER_UPDATE_RATE;
// The maximum distance, in Wc3 units, that each projectile can move.
public constant real FIRE_WAVE_PROJECTILE_MAX_DISTANCE = 1600.0;
// The rotation speed of each projectile, in Radians per second.
public constant real FIRE_WAVE_PROJECTILE_TURN_SPEED = 270.0 * TIMER_UPDATE_RATE * bj_DEGTORAD;
// =========================================================
// ------------------ Shield Configuration -----------------
// =========================================================
// The number of nodes that compose the shield.
public constant integer SHIELD_NODE_AMOUNT = 5;
// The ID of the unit that represents a shield node.
public constant integer SHIELD_NODE_UNIT_ID = 'h002';
// The special effects played when the nodes are created.
public constant string SHIELD_NODE_SPAWN_SFX1 = "Abilities\\Weapons\\DemolisherFireMissile\\DemolisherFireMissile.mdl";
public constant string SHIELD_NODE_SPAWN_SFX2 = "Flame Burst.mdl";
// The models that make up the projectiles.
// These projectiles run from one node to the next one, continuously.
public constant string SHIELD_NODE_PROJECTILE_SFX1 = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl";
public constant string SHIELD_NODE_PROJECTILE_SFX2 = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl";
// The scale size of the projectiles in the shield. In this case, it means they will be 175% bigger.
public constant real SHIELD_NODE_PROJECTILE_SCALE = 1.75;
// The movement speed of each projectile, in Wc3 units per second.
public constant real SHIELD_NODE_PROJECTILE_SPEED = 1200.0 * TIMER_UPDATE_RATE;
// Each node is linked to the next node, in a straight line. Every enemy unit that is close enough to that
// link will be knocked back.
// This variable determines the distance, in Wc3 units, that each enemy needs to be in order to be knocked and
// suffer damage.
public constant real SHIELD_COLLISION_RADIUS = 50;
// The ID of the unit that represents the avatar, the creature that is spawned in the position of the caster.
public constant integer SHIELD_AVATAR_ID = 'h001';
// Determines the opacity of the avatar. 0 means fully transparent and 255 opaque.
public constant integer SHIELD_AVATAR_OPACITY = 190;
// =========================================================
// ---------------- Knockback Configuration ----------------
// =========================================================
// The movement speed that each unit will be knocked back, in Wc3 units per second.
public constant real KNOCKBACK_SPEED = 1500.0 * TIMER_UPDATE_RATE;
// The friction factor applied to the knockback speed, making the knockback effect gradually stop.
public constant real KNOCKBACK_FRICTION_FACTOR = 0.9;
// The special effect played eveytime a unit moves when being knocked back.
public constant string KNOCKBACK_SFX = "Small Dust.mdl";
// =========================================================
// ------------------ Dummy Configuration ------------------
// =========================================================
// The ID of the dummy unit used for projectiles.
public constant integer DUMMY_ID = 'h000';
// The ID of the Locust ability to make dummies untargetable.
public constant integer LOCUST_SKILL_ID = 'Aloc';
// The ID of the Crow Form ability to change units' flying height.
public constant integer FLYING_SKILL_ID = 'Amrf';
// =========================================================
// ------------------ Other Configuration ------------------
// =========================================================
// Determines whether trees are destroyed when the shield is fully created and when units are knocked back.
// If this is set to false, knocked units will stop when encountering trees.
public constant boolean DESTROY_TREES = true;
// The collision radius when units are knocked against trees.
public constant real TREE_COLLISION_RADIUS = 100.0;
// The ability ID used to detect if the destructible is a tree.
public constant integer TREE_HARVEST_ID = 'Ahrl';
// The order ID to harvest a tree.
public constant integer TREE_HARVEST_ORDER_ID = 0xD0032;
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! zinc
library BlazeGuardian requires BlazeGuardianConfig {
private interface IUpdateable {
integer ID;
method Update() = null;
}
private type UnitEventHandler extends function(unit);
private struct SpellInstance extends IUpdateable {
public static group IgnoreGroup = CreateGroup();
private static SpellInstance Current;
private static group UtilGroup = CreateGroup();
public unit Caster;
public real CasterX;
public real CasterY;
public player Owner;
public real AreaOfEffect;
public real KnockbackDamage;
public real ShieldDamage;
public real ShieldDuration;
public unit Avatar;
private real TimeLeft;
private real SfxCooldown;
private boolean PlayAnimation;
private integer WaveCount;
private Node Nodes[SHIELD_NODE_AMOUNT];
private NodeProjectile NodeProjectiles[SHIELD_NODE_AMOUNT];
private integer Stage;
public static method create(unit caster) -> thistype {
thistype this = thistype.allocate();
integer spellLevel;
this.Caster = caster;
this.CasterX = GetUnitX(caster);
this.CasterY = GetUnitY(caster);
PauseUnit(this.Caster, true);
this.Owner = GetOwningPlayer(this.Caster);
spellLevel = GetUnitAbilityLevel(this.Caster, SPELL_ID);
this.AreaOfEffect = SpellAreaOfEffectByLevel(spellLevel);
this.KnockbackDamage = SpellKnockbackDamageByLevel(spellLevel);
this.ShieldDamage = SpellShieldDamageByLevel(spellLevel);
this.ShieldDuration = SpellDurationByLevel(spellLevel);
this.Stage = 1;
this.TimeLeft = SPELL_CHARGE_DURATION;
this.SfxCooldown = SPELL_CASTING_SFX_INTERVAL;
this.PlayAnimation = true;
this.WaveCount = FIRE_WAVE_COUNT;
EntityManager.Add(this);
return this;
}
public method Update() {
if (IsUnitType(this.Caster, UNIT_TYPE_DEAD)) {
this.destroy();
return;
}
thistype.Current = this;
if (this.Stage == 1) {
this.Stage1();
} else if (this.Stage == 2) {
this.Stage2();
} else if (this.Stage == 3) {
this.Stage3();
}
this.SfxCooldown -= TIMER_UPDATE_RATE;
if (this.SfxCooldown <= 0) {
DestroyEffect(AddSpecialEffect(SPELL_CASTING_SFX1, this.CasterX, this.CasterY));
DestroyEffect(AddSpecialEffect(SPELL_CASTING_SFX2, this.CasterX, this.CasterY));
this.SfxCooldown = SPELL_CASTING_SFX_INTERVAL;
}
}
private method Stage1() {
integer i;
real angle;
// Animations won't play right after the casting the spell so we wait 0.03 seconds to play the
// channel animation.
if (this.PlayAnimation) {
SetUnitAnimation(this.Caster, SPELL_CASTING_ANIMATION);
this.PlayAnimation = false;
}
this.TimeLeft -= TIMER_UPDATE_RATE;
if (this.TimeLeft <= 0) {
this.Stage = 2;
this.TimeLeft = FIRE_WAVE_DELAY;
}
}
private method Stage2() {
integer i, j;
real angle, x, y, facing;
this.TimeLeft -= TIMER_UPDATE_RATE;
if (this.TimeLeft <= 0) {
if (this.WaveCount > 0) {
this.TimeLeft = FIRE_WAVE_DELAY;
this.WaveCount -= 1;
angle = (2.0 * bj_PI) / FIRE_WAVE_PROJECTILE_AMOUNT;
for (i = 0; i < FIRE_WAVE_PROJECTILE_AMOUNT; i += 1) {
WaveProjectile.create(this.CasterX, this.CasterY, angle * i, FIRE_WAVE_PROJECTILE_MAX_DISTANCE);
}
GroupEnumUnitsInRange(thistype.UtilGroup, this.CasterX, this.CasterY, this.AreaOfEffect, function() -> boolean {
unit filterUnit = GetFilterUnit();
Knockback knock;
if (!IsUnitInGroup(filterUnit, thistype.IgnoreGroup) && !IsUnitType(filterUnit, UNIT_TYPE_DEAD) && IsUnitEnemy(filterUnit, Current.Owner) && !IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE)) {
GroupAddUnit(thistype.IgnoreGroup, filterUnit);
UnitDamageTarget(Current.Caster, filterUnit, Current.KnockbackDamage, true, true, SPELL_ATTACK_TYPE, SPELL_DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS);
knock = Knockback.create(filterUnit, Atan2(GetUnitY(filterUnit) - Current.CasterY, GetUnitX(filterUnit) - Current.CasterX));
knock.OnEnd = function(unit target) {
GroupRemoveUnit(thistype.IgnoreGroup, target);
};
}
filterUnit = null;
return false;
});
} else {
this.Stage = 3;
facing = GetUnitFacing(this.Caster) * bj_DEGTORAD;
this.Avatar = CreateUnit(this.Owner, SHIELD_AVATAR_ID, this.CasterX, this.CasterY, facing * bj_RADTODEG);
PauseUnit(this.Avatar, true);
SetUnitAnimation(this.Avatar, "Stand Swim");
x = this.CasterX - 50.0 * Cos(facing);
y = this.CasterY - 50.0 * Sin(facing);
SetUnitX(this.Avatar, x);
SetUnitY(this.Avatar, y);
SetUnitVertexColor(this.Avatar, 255, 255, 255, 0);
FadeInEffect.create(this.Avatar, 0.25, SHIELD_AVATAR_OPACITY);
angle = (360.0 * bj_DEGTORAD) / SHIELD_NODE_AMOUNT;
for (i = 0; i < SHIELD_NODE_AMOUNT; i += 1) {
x = this.CasterX + this.AreaOfEffect * Cos(facing + angle * i);
y = this.CasterY + this.AreaOfEffect * Sin(facing + angle * i);
this.Nodes[i] = Node.create(x, y);
}
for (i = 0; i < SHIELD_NODE_AMOUNT; i += 1) {
j = i + 1;
if (j >= SHIELD_NODE_AMOUNT) {
j = 0;
}
this.Nodes[i].NextNode = this.Nodes[j];
this.NodeProjectiles[i] = NodeProjectile.create(this.Nodes[i]);
}
static if (DESTROY_TREES) {
Utils.DestroyTrees(this.CasterX, this.CasterY, this.AreaOfEffect);
}
}
}
}
private method Stage3() {
this.ShieldDuration -= TIMER_UPDATE_RATE;
if (this.ShieldDuration > 0) {
GroupEnumUnitsInRange(thistype.UtilGroup, this.CasterX, this.CasterY, this.AreaOfEffect + 100, function() -> boolean {
integer i;
real x, y, angle;
unit filterUnit = GetFilterUnit();
Knockback knock;
if (!IsUnitInGroup(filterUnit, thistype.IgnoreGroup) && !IsUnitType(filterUnit, UNIT_TYPE_DEAD) && IsUnitEnemy(filterUnit, Current.Owner) && !IsUnitType(filterUnit, UNIT_TYPE_STRUCTURE)) {
x = GetUnitX(filterUnit);
y = GetUnitY(filterUnit);
if (Current.CollidesWithShield(x, y)) {
GroupAddUnit(thistype.IgnoreGroup, filterUnit);
angle = Atan2(y - Current.CasterY, x - Current.CasterX);
UnitDamageTarget(Current.Caster, filterUnit, Current.ShieldDamage, true, true, SPELL_ATTACK_TYPE, SPELL_DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS);
knock = Knockback.create(filterUnit, angle);
knock.OnEnd = function(unit target) {
GroupRemoveUnit(thistype.IgnoreGroup, target);
};
}
}
filterUnit = null;
return false;
});
} else {
this.destroy();
}
}
private method CollidesWithShield(real x, real y) -> boolean {
integer i;
real dist1, dist2, dist3;
for (i = 0; i < SHIELD_NODE_AMOUNT; i += 1) {
dist1 = SquareRoot((x - this.Nodes[i].X) * (x - this.Nodes[i].X) + (y - this.Nodes[i].Y) * (y - this.Nodes[i].Y));
dist2 = SquareRoot((this.Nodes[i].NextNode.X - x) * (this.Nodes[i].NextNode.X - x) + (this.Nodes[i].NextNode.Y - y) * (this.Nodes[i].NextNode.Y - y));
dist3 = SquareRoot((this.Nodes[i].NextNode.X - this.Nodes[i].X) * (this.Nodes[i].NextNode.X - this.Nodes[i].X) + (this.Nodes[i].NextNode.Y - this.Nodes[i].Y) * (this.Nodes[i].NextNode.Y - this.Nodes[i].Y));
if ((dist1 + dist2 - dist3) <= SHIELD_COLLISION_RADIUS) {
return true;
}
}
return false;
}
private method onDestroy() {
integer i;
if (this.Avatar != null) {
FadeOutEffect.create(this.Avatar, 0.5, SHIELD_AVATAR_OPACITY);
}
EntityManager.Remove(this);
PauseUnit(this.Caster, false);
SetUnitAnimation(this.Caster, "stand");
for (i = 0; i < SHIELD_NODE_AMOUNT; i += 1) {
this.NodeProjectiles[i].destroy();
this.Nodes[i].destroy();
}
}
}
private struct WaveProjectile extends IUpdateable {
private unit Dummy;
private real X;
private real Y;
private effect Sfx;
private real Distance;
private real Angle;
private real AngleCos;
private real AngleSin;
public static method create(real x, real y, real angle, real distance) -> thistype {
thistype this = thistype.allocate();
this.Dummy = DummyPool.Get();
this.X = x;
this.Y = y;
this.Sfx = AddSpecialEffectTarget(FIRE_WAVE_PROJECTILE_SFX, this.Dummy, "chest");
SetUnitScale(this.Dummy, FIRE_WAVE_PROJECTILE_SCALE, FIRE_WAVE_PROJECTILE_SCALE, FIRE_WAVE_PROJECTILE_SCALE);
SetUnitX(this.Dummy, x);
SetUnitY(this.Dummy, y);
this.Distance = distance;
this.Angle = angle;
EntityManager.Add(this);
return this;
}
public method Update() {
this.Angle += FIRE_WAVE_PROJECTILE_TURN_SPEED;
SetUnitFacing(this.Dummy, this.Angle);
this.X += FIRE_WAVE_PROJECTILE_SPEED * Cos(this.Angle);
this.Y += FIRE_WAVE_PROJECTILE_SPEED * Sin(this.Angle);
SetUnitX(this.Dummy, this.X);
SetUnitY(this.Dummy, this.Y);
this.Distance -= FIRE_WAVE_PROJECTILE_SPEED;
if (this.Distance <= 0) {
this.destroy();
}
}
private method onDestroy() {
DestroyEffect(this.Sfx);
DummyPool.Release(this.Dummy);
EntityManager.Remove(this);
}
}
private struct Knockback extends IUpdateable {
public UnitEventHandler OnEnd;
private unit Target;
private real X;
private real Y;
private real Speed;
private real Friction;
private real AngleCos;
private real AngleSin;
public static method create(unit target, real angle) -> thistype {
thistype this = thistype.allocate();
this.Target = target;
this.X = GetUnitX(this.Target);
this.Y = GetUnitY(this.Target);
if (KNOCKBACK_FRICTION_FACTOR >= 1.0) {
this.Friction = 0.9;
} else {
this.Friction = KNOCKBACK_FRICTION_FACTOR;
}
this.Speed = KNOCKBACK_SPEED;
this.AngleCos = Cos(angle);
this.AngleSin = Sin(angle);
EntityManager.Add(this);
return this;
}
public method Update() {
this.X += this.Speed * AngleCos;
this.Y += this.Speed * AngleSin;
SetUnitX(this.Target, this.X);
SetUnitY(this.Target, this.Y);
DestroyEffect(AddSpecialEffect(KNOCKBACK_SFX, this.X, this.Y));
this.Speed *= this.Friction;
static if (DESTROY_TREES) {
Utils.DestroyTrees(this.X, this.Y, TREE_COLLISION_RADIUS);
} else {
if (Utils.CollidesWithTrees(this.X, this.Y, TREE_COLLISION_RADIUS)) {
this.destroy();
return;
}
}
if (!Utils.IsTerrainLand(this.X, this.Y) || this.Speed < 1.0) {
this.destroy();
}
}
private method onDestroy() {
this.OnEnd.evaluate(this.Target);
EntityManager.Remove(this);
}
}
private struct Node {
public real X;
public real Y;
public thistype NextNode;
private unit Dummy;
public static method create(real x, real y) -> thistype {
thistype this = thistype.allocate();
this.X = x;
this.Y = y;
this.Dummy = CreateUnit(Player(15), SHIELD_NODE_UNIT_ID, this.X, this.Y, 0.0);
DestroyEffect(AddSpecialEffect(SHIELD_NODE_SPAWN_SFX1, this.X, this.Y));
DestroyEffect(AddSpecialEffect(SHIELD_NODE_SPAWN_SFX2, this.X, this.Y));
return this;
}
private method onDestroy() {
FadeOutEffect.create(this.Dummy, 0.5, 255);
}
}
private struct NodeProjectile extends IUpdateable {
private unit Dummy;
private effect Sfx1;
private effect Sfx2;
private effect Sfx3;
private Node Target;
private real X;
private real Y;
private real Distance;
private real AngleCos;
private real AngleSin;
public static method create(Node node) -> thistype {
thistype this = thistype.allocate();
this.Dummy = DummyPool.Get();
this.Sfx1 = AddSpecialEffectTarget(SHIELD_NODE_PROJECTILE_SFX1, this.Dummy, "chest");
this.Sfx2 = AddSpecialEffectTarget(SHIELD_NODE_PROJECTILE_SFX2, this.Dummy, "chest");
this.Sfx3 = AddSpecialEffectTarget(SHIELD_NODE_PROJECTILE_SFX2, this.Dummy, "chest");
this.X = node.X;
this.Y = node.Y;
SetUnitX(this.Dummy, this.X);
SetUnitY(this.Dummy, this.Y);
SetUnitScale(this.Dummy, SHIELD_NODE_PROJECTILE_SCALE, SHIELD_NODE_PROJECTILE_SCALE, SHIELD_NODE_PROJECTILE_SCALE);
SetUnitFlyHeight(this.Dummy, 50.0, 0.0);
this.AssignTarget(node.NextNode);
EntityManager.Add(this);
return this;
}
private method AssignTarget(Node node) {
real diffX, diffY, angle;
this.Target = node;
diffX = this.Target.X - this.X;
diffY = this.Target.Y - this.Y;
this.Distance = SquareRoot(diffX * diffX + diffY * diffY);
angle = Atan2(this.Target.Y - this.Y, this.Target.X - this.X);
this.AngleCos = Cos(angle);
this.AngleSin = Sin(angle);
SetUnitFacing(this.Dummy, angle);
}
public method Update() {
this.X += SHIELD_NODE_PROJECTILE_SPEED * this.AngleCos;
this.Y += SHIELD_NODE_PROJECTILE_SPEED * this.AngleSin;
SetUnitX(this.Dummy, this.X);
SetUnitY(this.Dummy, this.Y);
this.Distance -= SHIELD_NODE_PROJECTILE_SPEED;
if (this.Distance < SHIELD_NODE_PROJECTILE_SPEED) {
this.AssignTarget(this.Target.NextNode);
}
}
private method onDestroy() {
DummyPool.Release(this.Dummy);
DestroyEffect(this.Sfx1);
DestroyEffect(this.Sfx2);
DestroyEffect(this.Sfx3);
EntityManager.Remove(this);
}
}
private struct FadeInEffect extends IUpdateable {
private unit Target;
private integer Alpha;
private integer AlphaTarget;
private integer Amount;
public static method create(unit target, real duration, integer alpha) -> thistype {
thistype this = thistype.allocate();
this.Target = target;
this.Alpha = 0;
this.AlphaTarget = alpha;
if (this.AlphaTarget <= 0 || this.AlphaTarget > 255) {
this.AlphaTarget = 255;
}
this.Amount = R2I(this.AlphaTarget / duration * TIMER_UPDATE_RATE);
SetUnitVertexColor(this.Target, 255, 255, 255, this.Alpha);
EntityManager.Add(this);
return this;
}
public method Update() {
this.Alpha += this.Amount;
SetUnitVertexColor(this.Target, 255, 255, 255, this.Alpha);
if (this.Alpha >= this.AlphaTarget) {
EntityManager.Remove(this);
this.destroy();
}
}
}
private struct FadeOutEffect extends IUpdateable {
private unit Target;
private integer Alpha;
private integer Amount;
public static method create(unit target, real duration, integer alpha) -> thistype {
thistype this = thistype.allocate();
this.Target = target;
this.Alpha = alpha;
if (this.Alpha <= 0 || this.Alpha > 255) {
this.Alpha = 255;
}
this.Amount = R2I(this.Alpha / duration * TIMER_UPDATE_RATE);
SetUnitVertexColor(this.Target, 255, 255, 255, this.Alpha);
EntityManager.Add(this);
return this;
}
public method Update() {
this.Alpha -= this.Amount;
SetUnitVertexColor(this.Target, 255, 255, 255, this.Alpha);
if (this.Alpha < this.Amount) {
EntityManager.Remove(this);
RemoveUnit(this.Target);
this.destroy();
}
}
}
private struct EntityManager {
private static timer Timer = CreateTimer();
private static IUpdateable Entities[];
private static integer Count = 0;
public static method Add(IUpdateable entity) {
integer index = Count;
Entities[index] = entity;
entity.ID = index;
Count += 1;
if (index == 0) {
TimerStart(Timer, TIMER_UPDATE_RATE, true, function thistype.Update);
}
}
public static method Remove(IUpdateable entity) {
if (entity.ID >= 0 && entity.ID < Count) {
Count -= 1;
Entities[entity.ID] = Entities[Count];
Entities[entity.ID].ID = entity.ID;
entity.ID = -1;
if (Count == 0) {
PauseTimer(Timer);
}
}
}
private static method Update() {
integer i;
for (i = 0; i < Count; i += 1) {
Entities[i].Update();
}
debug {
ClearTextMessages();
BJDebugMsg("Count: " + I2S(thistype.Count));
}
}
}
private struct DummyPool {
private static unit Pool[];
private static integer Count = 0;
// Forces the creation of a new dummy unit, when facing angle is important
public static method New(real facingAngle) -> unit {
unit dummy = CreateUnit(Player(15), DUMMY_ID, 0.0, 0.0, facingAngle);
UnitAddAbility(dummy, FLYING_SKILL_ID);
UnitAddAbility(dummy, LOCUST_SKILL_ID);
UnitRemoveAbility(dummy, FLYING_SKILL_ID);
PauseUnit(dummy, true);
ShowUnit(dummy, true);
return dummy;
}
// Gets a dummy unit from the pool.
public static method Get() -> unit {
unit dummy;
if (Count == 0) {
dummy = New(0.0);
} else {
Count -= 1;
dummy = Pool[Count];
Pool[Count] = null;
ShowUnit(dummy, true);
}
return dummy;
}
// Release a dummy unit back to the pool
public static method Release(unit dummy) {
SetUnitOwner(dummy, Player(15), true);
SetUnitScale(dummy, 1.0, 1.0, 1.0);
SetUnitTimeScale(dummy, 1.0);
SetUnitFlyHeight(dummy, 0.0, 0.0);
SetUnitVertexColor(dummy, 255, 255, 255, 255);
ShowUnit(dummy, false);
Pool[Count] = dummy;
Count += 1;
}
private static method onInit() {
integer i;
for (i = 0; i < 30; i += 1) {
Pool[Count] = New(0.0);
Count += 1;
}
}
}
private struct Utils {
private static boolean Result;
private static unit Dummy;
public static method DestroyTrees(real x, real y, real radius) {
rect area = Rect(x - radius, y - radius, x + radius, y + radius);
EnumDestructablesInRect(area, null, function() {
destructable tree = GetEnumDestructable();
if (GetDestructableLife(tree) > 0.405) {
KillDestructable(tree);
}
tree = null;
});
RemoveRect(area);
area = null;
}
public static method CollidesWithTrees(real x, real y, real collision) -> boolean {
rect area = Rect(x - collision, y - collision, x + collision, y + collision);
thistype.Result = false;
EnumDestructablesInRect(area, null, function() {
destructable tree = GetEnumDestructable();
if (GetDestructableLife(tree) > 0.405 && IssueTargetOrderById(thistype.Dummy, TREE_HARVEST_ORDER_ID, tree)) {
thistype.Result = true;
}
tree = null;
});
RemoveRect(area);
area = null;
return thistype.Result;
}
public static method IsTerrainLand(real x, real y) -> boolean {
return !IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY);
}
private static method onInit() {
thistype.Dummy = DummyPool.Get();
ShowUnit(thistype.Dummy, false);
UnitAddAbility(thistype.Dummy, TREE_HARVEST_ID);
}
}
// Based on BoundSentinel by Vexorian
private struct MapLimits {
private static real BoundsMinX;
private static real BoundsMinY;
private static real BoundsMaxX;
private static real BoundsMaxY;
private static method onInit() {
trigger trig;
region reg;
rect bounds;
rect rc;
static if (!LIBRARY_BoundSentinel) {
trig = CreateTrigger();
reg = CreateRegion();
bounds = GetWorldBounds();
BoundsMinX = GetRectMinX(bounds);
BoundsMinY = GetRectMinY(bounds);
BoundsMaxY = GetRectMaxY(bounds);
BoundsMaxY = GetRectMaxY(bounds);
rc = Rect(BoundsMinX, BoundsMinY, BoundsMaxX, BoundsMaxY);
RegionAddRect(reg, rc);
TriggerRegisterLeaveRegion(trig, reg, null);
TriggerAddCondition(trig, Condition(function() -> boolean {
unit u = GetTriggerUnit();
real x = GetUnitX(u);
real y = GetUnitY(u);
if (x > BoundsMaxX){
x = BoundsMaxX;
} else if (x < BoundsMinX) {
x = BoundsMinX;
}
if (y > BoundsMaxY) {
y = BoundsMaxY;
} else if (y < BoundsMinY) {
y = BoundsMinY;
}
SetUnitX(u, x);
SetUnitY(u, y);
u = null;
return false;
}));
RemoveRect(rc);
RemoveRect(bounds);
rc = null;
bounds = null;
reg = null;
trig = null;
}
}
}
private function onInit() {
trigger spellCastTrigger = CreateTrigger();
integer i;
for (i = 0; i < 12; i += 1) {
TriggerRegisterPlayerUnitEvent(spellCastTrigger, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
}
TriggerAddCondition(spellCastTrigger, function() -> boolean { return GetSpellAbilityId() == SPELL_ID; } );
TriggerAddAction(spellCastTrigger, function() {
SpellInstance.create(GetTriggerUnit());
});
spellCastTrigger = null;
}
}
//! endzinc