Name | Type | is_array | initial_value |
A_SB_Instances | integer | No | |
A_SB_Integer | integervar | No | |
A_SB_Integer2 | integer | No | |
A_SB_Original_MoveSpeed | real | Yes | |
A_SB_Point | location | Yes | |
A_SB_Timer | integer | Yes | |
A_SB_Unit | unit | Yes | |
AAA_Player_Colors | string | Yes | |
AAAA_GP | location | No | |
AAAAA_JB_Chance | integer | No | |
AAAAA_JB_Target_1 | location | No | |
AAAAAA_SB_Integer | integer | No | |
AB_Caster | unit | Yes | |
AB_Damage | real | Yes | |
AB_Duration | real | Yes | |
AB_index | integer | Yes | |
AB_Indexsize | integer | No | |
AB_Loop | integervar | No | |
AB_maxindexsize | integer | No | |
AB_Orb | unit | Yes | |
AB_Point1 | location | No | |
AB_Point2 | location | No | |
AB_Point3 | location | No | |
AB_Point4 | location | No | |
AB_Temp | integer | No | |
AB_UG | group | Yes | |
Angle | real | Yes | |
Boolean | boolean | No | |
Boosting_Unit | unit | No | |
Boosting_UnitType | unitcode | No | |
Camera_Distance | integer | No | |
Camera_Unit | unit | No | |
Check1_Boolean | boolean | Yes | |
Check2_Boolean | boolean | Yes | |
Check3_Boolean | boolean | Yes | |
Check4_Boolean | boolean | Yes | |
Collision_Boolean | boolean | Yes | |
Collision_Group | group | Yes | |
Collision_Immune_Integer | integer | No | |
Collision_Integer | integer | No | |
Collision_Unit_Point | location | Yes | |
Comp_Integer | integer | No | |
Comp_Player_ID | integer | No | |
Comp_Random_Int | integer | No | |
Completed_Laps | integer | Yes | |
Completed_Racers | integer | No | |
Computer_Hero | unit | Yes | |
Computer_Point | location | Yes | |
Computer_Stage | integer | Yes | |
ComputerGroup | force | No | |
Current_Racers | group | No | |
EXAMPLE_DUR | real | Yes | |
General_Point | location | Yes | |
Goomba_Group | group | No | |
Goomba_Normal_Group | group | No | |
HM_Active | boolean | Yes | |
HM_Angle | real | Yes | |
HM_AngleChangeSpeed | real | Yes | |
HM_CurDistance | real | Yes | |
HM_Damage | real | Yes | |
HM_DamageDealer | unit | Yes | |
HM_DamageGroup | group | No | |
HM_LeakPoint | location | Yes | |
HM_LoopIndex | integer | Yes | |
HM_MaxDistance | real | Yes | |
HM_Missle | unit | Yes | |
HM_PinningDistance | real | Yes | |
HM_Speed | real | Yes | |
HM_TargetUnit | unit | Yes | |
HM_TempGroup | group | No | |
HM_TempReal | real | Yes | |
Human_Players | force | No | |
Integer | integer | No | |
Item_Index | integer | Yes | |
Item_Index_MaxSize | integer | No | |
Item_Index_Size | integer | No | |
Item_Instances | integer | No | |
Item_Integer | integer | No | |
Item_Player_Number | integer | No | |
Item_Point | location | Yes | |
Item_Spawn_Point | location | Yes | |
Item_TempInt | integer | No | |
Item_Timer | integer | Yes | |
Item_Unit | unit | Yes | |
ItemGain_Integer | integer | No | |
ItemGain_Timer | integer | Yes | |
JB_Angle | real | No | |
JB_Start_Point | location | No | |
JB_Unit_MoveSpeed | real | No | |
JD_Angle | real | Yes | |
JD_Animations | string | Yes | |
JD_Distances | real | Yes | |
JD_Effect | string | Yes | |
JD_Group | group | No | |
JD_HighSettings | real | Yes | |
JD_Integers | integer | Yes | |
JD_JumpHigh | real | Yes | |
JD_ReachedDistance | real | Yes | |
JD_RealTimer | real | Yes | |
JD_SpeedUnits | real | Yes | |
JD_TempPoint | location | Yes | |
JD_TreesDestroy | boolean | Yes | |
JD_Unit | unit | Yes | |
JDA_Animation | string | No | |
JDA_AnimationSpeed | real | No | |
JDA_Collusion | boolean | No | |
JDA_DestroyTrees_Dash | boolean | No | |
JDA_JumpHigh_Distance | real | No | |
JDA_SpecialEffect | string | No | |
JDA_Speed | real | No | |
JDA_TargetPoint | location | No | |
JDA_Unit | unit | No | |
LockedPlayers | force | No | |
MaxSpeed | real | Yes | |
Number_of_Computers | integer | No | |
Number_of_Items | integer | No | |
Ouch_Position | location | No | |
Ouch_Unit | unit | No | |
Ouch_Unit_Type | unitcode | No | |
Pengu_Group | group | No | |
Pengu_Point | location | No | |
PlayerGroup | force | No | |
Present | item | No | |
Present_Timer | timer | Yes | |
Race_Number | integer | No | |
Race_Start_Boolean | boolean | No | |
Racer_Unit | unit | Yes | |
Racer_Unit_Type | unitcode | Yes | |
Random_Hero_Type | unitcode | Yes | |
Random_Int | integer | No | |
Random_Item | itemcode | Yes | |
Recent_Item_Gain | boolean | Yes | |
Signal | timer | Yes | |
Snowman_Integer | integer | No | |
Snowman_Position | location | No | |
Snowman_Target | location | No | |
Snowmen_Group | group | No | |
StartingPoint_Integer | integer | No | |
Temp_Computer_Group | force | No | |
temp_integer_yeee | integer | No | |
Temp_Item_Group | group | No | |
Temp_Item_Unit | unit | No | |
Temp_Playa | player | No | |
Temp_PlayaNumba | integer | No | |
Temp_String | string | No | |
TempInt | integer | No | |
Thwomp_Group | group | No | |
Track_Timer | timer | No | |
Track_Timer_Window | timerdialog | No | |
Unit_Current_Point | location | No | |
VO_Angle | real | Yes | |
VO_Boolean | boolean | Yes | |
VO_Caster | unit | Yes | |
VO_Damage | real | Yes | |
VO_Duration | real | Yes | |
VO_index | integer | Yes | |
VO_Point | location | Yes | |
VO_Speed | real | Yes | |
VO_Victom | unit | Yes | |
Weight | integer | Yes | |
Weight_Player | player | Yes | |
Weight_Player_Number | integer | No | |
Winning_Playa_Number | integer | No | |
Zapped_Unit_Group | group | No | |
Zoom | real | Yes |
//TESH.scrollpos=176
//TESH.alwaysfold=0
//! zinc
library CameraSystem requires optional CameraKeyboardMovement
{
//////////////////////////////////////////////////////////////////////////
//
// Configuration area
// - Just default variables which the system will use when no other ones are specified by the user.
constant real CAMERA_DEFAULT_X = 0,
CAMERA_DEFAULT_Y = 0,
CAMERA_DEFAULT_ZANGLE = 350,
CAMERA_DEFAULT_ZHEIGHT = 190,
CAMERA_DEFAULT_ROTATION = 0,
CAMERA_DEFAULT_DISTANCE = 175,
// - This is tricky. It defines the weight the measurements of the z position have. This system measures
// out 4 locations to get a neat z height, 2 of them are at the unit's location and 2 are at the camera
// location which is a bit further away. Those values define which measure has more weight - if you have
// low terrain with less cliff heights you should probably give the unit's measurement more weight.
// If you don't understand a bit of what I'm trying to explain, just play around a bit with those values
// and look what effect they might have.
CAMERA_TARGET_WEIGHT = 1,
CAMERA_CAMERA_WEIGHT = 2 - CAMERA_TARGET_WEIGHT,
// - What difference the measures have, if you have really steep cliffs (I'm not talking about blizzard cliffs)
// you should probably decrease this.
CAMERA_ZDIST_MEASURE = 50,
// - Pretty much self-explaining. You don't really have to change anything here.
CAMERA_LOOP_INTERVAL = 0.01,
CAMERA_UPDATE_INTERVAL = 0.2;
//////////////////////////////////////////////////////////////////////////
//
// Struct area
public struct CAMERA
{
//////////////////////////////////////////////////////////////////////////
//
// Struct variable area
public
{
real x = CAMERA_DEFAULT_X, y = CAMERA_DEFAULT_Y,
zAngle = CAMERA_DEFAULT_ZANGLE, zHeight = CAMERA_DEFAULT_ZHEIGHT,
rotation = CAMERA_DEFAULT_ROTATION, distanceToTarget = CAMERA_DEFAULT_DISTANCE;
player applyFor = null;
unit toFollow = null;
}
private
{
integer node;
boolean activated = false;
static timer t = CreateTimer();
static integer count = 0;
static thistype data[];
static location loc = Location(0,0);
}
optional module CameraKeyboardMovement;
//////////////////////////////////////////////////////////////////////////
//
// Struct method area
static method create(player p) -> thistype
{
thistype this = thistype.allocate();
applyFor = p;
static if (thistype.movementInit.exists) { movementInit(); }
return this;
}
method apply()
{
if (count == 0)
TimerStart(t, CAMERA_LOOP_INTERVAL, true, static method thistype.cameraLoop);
data[count] = this;
node = count;
count += 1;
activated = true;
}
private static method cameraLoop()
{
real tX = 0, tY = 0, tZ[], tzAngle = 0, tzHeight = 0, tRotation = 0;
thistype this;
integer i = 0;
while (i < count)
{
this = data[i];
static if (thistype.movement.exists) { movement(); }
if (toFollow != null)
{
x = GetUnitX(toFollow); y = GetUnitY(toFollow);
tRotation = GetUnitFacing(toFollow)*bj_DEGTORAD;
tzHeight = GetUnitFlyHeight(toFollow);
}
tX = x; tY = y;
tX += CAMERA_ZDIST_MEASURE/2 * Cos(tRotation);
tY += CAMERA_ZDIST_MEASURE/2 * Sin(tRotation);
MoveLocation(loc, tX, tY);
tZ[0] = GetLocationZ(loc);
tX -= CAMERA_ZDIST_MEASURE * Cos(tRotation);
tY -= CAMERA_ZDIST_MEASURE * Sin(tRotation);
MoveLocation(loc, tX, tY);
tZ[1] = GetLocationZ(loc);
tX = GetCameraTargetPositionX() + CAMERA_ZDIST_MEASURE/2 * Cos(tRotation);
tY = GetCameraTargetPositionY() + CAMERA_ZDIST_MEASURE/2 * Sin(tRotation);
MoveLocation(loc, tX, tY);
tZ[2] = GetLocationZ(loc);
tX -= CAMERA_ZDIST_MEASURE * Cos(tRotation);
tY -= CAMERA_ZDIST_MEASURE * Sin(tRotation);
MoveLocation(loc, tX, tY);
tZ[3] = GetLocationZ(loc);
tZ[2] = ((tZ[0]-tZ[1])*CAMERA_TARGET_WEIGHT+(tZ[2]-tZ[3])*CAMERA_CAMERA_WEIGHT)/4;
tX = x; tY = y;
tRotation += rotation*bj_DEGTORAD;
tX -= (distanceToTarget+tZ[2]) * Cos(tRotation);
tY -= (distanceToTarget+tZ[2]) * Sin(tRotation);
MoveLocation(loc, tX, tY);
tzAngle = zAngle + tZ[2];
tzHeight += zHeight + GetCameraField(CAMERA_FIELD_ZOFFSET) + GetLocationZ(loc) + RAbsBJ(tZ[2]) - GetCameraEyePositionZ();
if (GetLocalPlayer() == applyFor)
{
PanCameraToTimed(tX, tY, CAMERA_UPDATE_INTERVAL);
SetCameraField(CAMERA_FIELD_ZOFFSET, tzHeight, CAMERA_UPDATE_INTERVAL);
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, tzAngle, CAMERA_UPDATE_INTERVAL);
SetCameraField(CAMERA_FIELD_ROTATION, tRotation*bj_RADTODEG, CAMERA_UPDATE_INTERVAL);
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, distanceToTarget+tZ[2], CAMERA_UPDATE_INTERVAL);
}
i += 1;
}
}
method operator active() -> boolean
{ return activated; }
method operator active=(boolean b)
{
if (b && !activated)
apply();
else if (!b && activated)
{
data[node] = data[count-1];
data[node].node = node;
count -= 1;
if (count == 0)
PauseTimer(t);
activated = false;
}
}
method destroy()
{
if (activated)
{
data[node] = data[count-1];
data[node].node = node;
count -= 1;
if (count == 0)
PauseTimer(t);
}
deallocate();
}
}
}
//! endzinc
//TESH.scrollpos=9
//TESH.alwaysfold=0
//! zinc
library CameraKeyboardMovement
{
//CONFIGURABLES
//How much a unit should turn around when pressing left/right. Leave the * bj_DEGTORAD, or it won't work.
//Default is the unit's turn speed, if you want to leave that, just set TURN_ANGLE to 0.
constant real TURN_ANGLE = 15 * bj_DEGTORAD;
//Animation that the unit get's when no key is pressed.
constant string ANIM_STOP = "stand";
//Default animation of a unit if none is found.
constant integer ANIM_DEFAULT = 6;
//Same for this library, because the cam variables are private. (And should stay so)
constant real LOOP_INTERVAL = 0.01,
UPDATE_INTERVAL = 0.2;
function CanUnitMove(unit who) -> boolean
{
return UnitAlive(who) && !IsUnitPaused(who) && !(GetUnitAbilityLevel(who, 'Bens') > 0) && /*
*/!(GetUnitAbilityLevel(who, 'Bena') > 0) && !(GetUnitAbilityLevel(who, 'Beng') > 0) && /*
*/!(GetUnitAbilityLevel(who, 'BSTN') > 0) && !(GetUnitAbilityLevel(who, 'BPSE') > 0);
}
//CONFIGURABLES
CAMERA TriggerCam = -1;
public CAMERA PlayerCameras[];
public function GetTriggerCam() -> CAMERA
{
return TriggerCam;
}
public module CameraKeyboardMovement
{
integer animationIndex = ANIM_DEFAULT;
real animationDur = 0;
private
{
boolean upPressed, downPressed, rightPressed, leftPressed, playAnimation;
trigger onKeyEvent;
real animationCount = 0;
}
method movementInit()
{
upPressed = false; downPressed = false; rightPressed = false; leftPressed = false; playAnimation = false;
onKeyEvent = CreateTrigger();
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_UP_DOWN);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_UP_UP);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_DOWN_DOWN);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_DOWN_UP);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_RIGHT_DOWN);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_RIGHT_UP);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_LEFT_DOWN);
TriggerRegisterPlayerEvent(onKeyEvent, applyFor, EVENT_PLAYER_ARROW_LEFT_UP);
PlayerCameras[GetPlayerId(applyFor)] = this;
registerKeyEvent(function()
{
TriggerCam = PlayerCameras[GetPlayerId(GetTriggerPlayer())];
if (GetTriggerEventId() == EVENT_PLAYER_ARROW_UP_DOWN) {
TriggerCam.upPressed = true;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_UP_UP) {
TriggerCam.upPressed = false;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_DOWN_DOWN) {
TriggerCam.downPressed = true;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_DOWN_UP) {
TriggerCam.downPressed = false;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_RIGHT_DOWN) {
TriggerCam.rightPressed = true;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_RIGHT_UP) {
TriggerCam.rightPressed = false;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_LEFT_DOWN) {
TriggerCam.leftPressed = true;
} else if (GetTriggerEventId() == EVENT_PLAYER_ARROW_LEFT_UP) {
TriggerCam.leftPressed = false;
}
});
}
method movement()
{
real x, y, ox, oy, facing, movespeed;
if (CanUnitMove(toFollow))
{
x = GetUnitX(toFollow); y = GetUnitY(toFollow); facing = GetUnitFacing(toFollow)*bj_DEGTORAD;
ox = x; oy = y;
movespeed = GetUnitMoveSpeed(toFollow);
if (upPressed && !downPressed)
{
playAnimation = true;
if (rightPressed && !leftPressed)
{
x += movespeed * LOOP_INTERVAL * Cos(facing - GetUnitDefaultTurnSpeed(toFollow));
y += movespeed * LOOP_INTERVAL * Sin(facing - GetUnitDefaultTurnSpeed(toFollow));
}
else if (!rightPressed && leftPressed)
{
x += movespeed * LOOP_INTERVAL * Cos(facing + GetUnitDefaultTurnSpeed(toFollow));
y += movespeed * LOOP_INTERVAL * Sin(facing + GetUnitDefaultTurnSpeed(toFollow));
}
else
{
x += movespeed * LOOP_INTERVAL * Cos(facing);
y += movespeed * LOOP_INTERVAL * Sin(facing);
}
SetUnitPosition(toFollow, x, y);
if (RAbsBJ(GetUnitX(toFollow) - x) > 0.5 || RAbsBJ(GetUnitY(toFollow) - y) > 0.5)
{
SetUnitPosition(toFollow, x, oy);
if (RAbsBJ(GetUnitX(toFollow) - x) > 0.5)
{
SetUnitPosition(toFollow, ox, y);
if (RAbsBJ(GetUnitY(toFollow) - y) > 0.5)
{
SetUnitPosition(toFollow, x, y);
}
}
}
}
else
{
if (playAnimation)
{
playAnimation = false;
SetUnitAnimation(toFollow, "stand");
}
}
if (rightPressed && !leftPressed)
{
if (TURN_ANGLE == 0)
{
SetUnitFacingTimed(toFollow, (facing - GetUnitDefaultTurnSpeed(toFollow)) * bj_RADTODEG, UPDATE_INTERVAL);
}
else
{
SetUnitFacingTimed(toFollow, (facing - TURN_ANGLE) * bj_RADTODEG, UPDATE_INTERVAL);
}
}
else if (!rightPressed && leftPressed)
{
if (TURN_ANGLE == 0)
{
SetUnitFacingTimed(toFollow, (facing + GetUnitDefaultTurnSpeed(toFollow)) * bj_RADTODEG, UPDATE_INTERVAL);
}
else
{
SetUnitFacingTimed(toFollow, (facing + TURN_ANGLE) * bj_RADTODEG, UPDATE_INTERVAL);
}
}
animationCount += LOOP_INTERVAL;
if (animationCount >= animationDur && playAnimation)
{
SetUnitAnimationByIndex(toFollow, animationIndex);
animationCount = 0;
}
}
}
method registerKeyEvent(code cb) -> triggeraction
{
return TriggerAddAction(onKeyEvent, cb);
}
method unregisterKeyEvent(triggeraction ta)
{
TriggerRemoveAction(onKeyEvent, ta);
}
}
}
//! endzinc
native UnitAlive takes unit whichUnit returns boolean
//TESH.scrollpos=20
//TESH.alwaysfold=0
library CamExampele initializer init requires CameraSystem
private function rotate takes nothing returns nothing
// If the pressed key was down, we will rotate the unit by 180°
// GetTriggerEventId() returns the id so we can check which arrow was pressed or released.
if GetTriggerEventId() == EVENT_PLAYER_ARROW_DOWN_DOWN then
call SetUnitFacing(PlayerCameras[GetPlayerId(GetTriggerPlayer())].toFollow, GetUnitFacing(PlayerCameras[GetPlayerId(GetTriggerPlayer())].toFollow) + 180)
endif
endfunction
private function playa takes nothing returns boolean
return ( GetPlayerController(GetFilterPlayer()) == MAP_CONTROL_USER )
endfunction
private function init takes nothing returns nothing
local group g = CreateGroup()
local unit u
local force f = CreateForce()
local CAMERA cam
set f = GetPlayersMatching(Condition(function playa))
if CountPlayersInForceBJ(f) == 1 then
// Creating the camera struct for Player 1 (Red)
set cam = CAMERA.create(Player(0))
call CreateUnitAtLoc(Player(0), 'H005', GetRectCenter(gg_rct_1start), 90)
call BJDebugMsg("Singleplayer 3D mode detected!")
// Getting the hero of Player 1
call GroupEnumUnitsInRange(g, 0, 0, 999999, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
if GetUnitTypeId(u) == 'H00D' then
exitwhen true
endif
if GetUnitTypeId(u) == 'H005' then
exitwhen true
endif
if GetUnitTypeId(u) == 'H00B' then
exitwhen true
endif
if GetUnitTypeId(u) == 'H003' then
exitwhen true
endif
if GetUnitTypeId(u) == 'H00E' then
exitwhen true
endif
if GetUnitTypeId(u) == 'H004' then
exitwhen true
endif
call GroupRemoveUnit(g, u)
endloop
call DestroyGroup(g)
// Setting the unit which the camera follows to the hero we just found out
set cam.toFollow = u
// Change the camera to active
set cam.active = true
// This is a member of the keyboard plugin. We have to set a animationIndex (for most units) and a animation duration (for all units).
// How do you get the index? Seriously. No idea. Play around with numbers from 0 to whatever until you get a good-looking walk
// animation
// How do you get the animation duration?
// Place a unit in the editor, click on it and click through it's animations until you find the walk animation. Write down the duration
// and you're done.
set cam.animationDur = 0.733
// Registering a new key event is easy - just like this.
call cam.registerKeyEvent(function rotate)
// Nulling is cool.
set g = null
set u = null
endif
endfunction
endlibrary
//TESH.scrollpos=21
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=3
//TESH.alwaysfold=0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Timer32 ~~ By Jesus4Lyf ~~ Version 1.06 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is Timer32?
// - Timer32 implements a fully optimised timer loop for a struct.
// - Instances can be added to the loop, which will call .periodic every
// PERIOD until .stopPeriodic() is called.
//
// =Pros=
// - Efficient.
// - Simple.
//
// =Cons=
// - Only allows one period.
// - The called method must be named ".periodic".
//
// Methods:
// - struct.startPeriodic()
// - struct.stopPeriodic()
//
// - private method periodic takes nothing returns nothing
//
// This must be defined in structs that implement Periodic Module.
// It will be executed by the module every PERIOD until .stopPeriodic() is called.
// Put "implement T32x" BELOW this method.
//
// Modules:
// - T32x
// Has no safety on .stopPeriodic or .startPeriodic (except debug messages
// to warn).
//
// - T32xs
// Has safety on .stopPeriodic and .startPeriodic so if they are called
// multiple times, or while otherwise are already stopped/started respectively,
// no error will occur, the call will be ignored.
//
// - T32
// The original, old version of the T32 module. This remains for backwards
// compatability, and is deprecated. The periodic method must return a boolean,
// false to continue running or true to stop.
//
// Details:
// - Uses one timer.
//
// - Do not, within a .periodic method, follow a .stopPeriodic call with a
// .startPeriodic call.
//
// How to import:
// - Create a trigger named T32.
// - Convert it to custom text and replace the whole trigger text with this.
//
// Thanks:
// - Infinitegde for finding a bug in the debug message that actually altered
// system operation (when in debug mode).
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library T32 initializer OnInit
globals
public constant real PERIOD=0.03125
public constant integer FPS=R2I(1/PERIOD)
public integer Tick=0 // very useful.
//==============================================================================
private trigger Trig=CreateTrigger()
endglobals
//==============================================================================
// The standard T32 module, T32x.
//
module T32x
private thistype next
private thistype prev
private static method PeriodicLoop takes nothing returns boolean
local thistype this=thistype(0).next
loop
exitwhen this==0
call this.periodic()
set this=this.next
endloop
return false
endmethod
method startPeriodic takes nothing returns nothing
debug if this.prev!=0 or thistype(0).next==this then
debug call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had startPeriodic called while already running!")
debug endif
set thistype(0).next.prev=this
set this.next=thistype(0).next
set thistype(0).next=this
set this.prev=thistype(0)
endmethod
method stopPeriodic takes nothing returns nothing
debug if this.prev==0 and thistype(0).next!=this then
debug call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had stopPeriodic called while not running!")
debug endif
// This is some real magic.
set this.prev.next=this.next
set this.next.prev=this.prev
// This will even work for the starting element.
debug set this.prev=0
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
endmethod
endmodule
//==============================================================================
// The standard T32 module with added safety checks on .startPeriodic() and
// .stopPeriodic(), T32xs.
//
module T32xs
private thistype next
private thistype prev
private boolean runningPeriodic
private static method PeriodicLoop takes nothing returns boolean
local thistype this=thistype(0).next
loop
exitwhen this==0
call this.periodic()
set this=this.next
endloop
return false
endmethod
method startPeriodic takes nothing returns nothing
if not this.runningPeriodic then
set thistype(0).next.prev=this
set this.next=thistype(0).next
set thistype(0).next=this
set this.prev=thistype(0)
set this.runningPeriodic=true
endif
endmethod
method stopPeriodic takes nothing returns nothing
if this.runningPeriodic then
// This is some real magic.
set this.prev.next=this.next
set this.next.prev=this.prev
// This will even work for the starting element.
set this.runningPeriodic=false
endif
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
endmethod
endmodule
//==============================================================================
// The original T32 module, for backwards compatability only.
//
module T32 // deprecated.
private thistype next
private thistype prev
private static method PeriodicLoop takes nothing returns boolean
local thistype this=thistype(0).next
loop
exitwhen this==0
if this.periodic() then
// This is some real magic.
set this.prev.next=this.next
set this.next.prev=this.prev
// This will even work for the starting element.
debug set this.prev=0
endif
set this=this.next
endloop
return false
endmethod
method startPeriodic takes nothing returns nothing
debug if this.prev!=0 or thistype(0).next==this then
debug call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had startPeriodic called while already running!")
debug endif
set thistype(0).next.prev=this
set this.next=thistype(0).next
set thistype(0).next=this
set this.prev=thistype(0)
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
endmethod
endmodule
//==============================================================================
// System Core.
//
private function OnExpire takes nothing returns nothing
set Tick=Tick+1
call TriggerEvaluate(Trig)
endfunction
private function OnInit takes nothing returns nothing
call TimerStart(CreateTimer(),PERIOD,true,function OnExpire)
endfunction
endlibrary
//TESH.scrollpos=53
//TESH.alwaysfold=0
// Elune's arrow by Ruke
// ---------------------
//
// REQUIRES: TimerUtils -> http://www.hiveworkshop.com/forums/submissions-414/system-timerutils-204500/
// REQUIRES: Timer32 -> http://www.thehelper.net/forums/showthread.php/132538-Timer32
//
// The spell does what?
// --------------------
// Fires an arrow to a location with deadly precision, dealing large damage and stunning
// the first unit it strikes. Stun duration increases based on how far the target
// is, ranging from 0.5 to 5 seconds. Has 3000 range.
// More information: http://www.playdota.com/heroes/priestess-of-the-moon#skill178
//
// How to import?
// --------------
// 1 ) Copy this trigger
// 2 ) Copy the arrow dummy unit (Object Editor -> Unit)
// 3 ) Copy Stun Elune (Object Editor -> Spell)
// 4 ) Copy Elune's Arrow (Object Editor -> Spell)
// 5 ) Copy the dummy unit (Object Editor -> Unit)
library EluneIsArrow requires TimerUtils, T32
//==========================================================================================//
// CONFIGURABLE //
//==========================================================================================//
globals
private constant integer DUMMY = 'h00F' // Stun's dummy
private constant integer DUMMY_ARROW = 'h00G' // Arrow's dummy
private constant integer STUN_RAWCODE = 'A00H' // Stun's spell
private constant integer STUN_BUFF = 'BPSE' // Stun's buff
private constant integer SPELL_RAWCODE = 'A00I' // Spell's rawcode
private constant damagetype DMG_TYPE = DAMAGE_TYPE_MAGIC // Damage type
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL // Attack type
private constant boolean AFFECT_INVIS = false // Affects the stun to invisible units?
endglobals
// Returns the spell's damage
private constant function GetDamage takes integer level returns real
return 50.
endfunction
// Returns the max distance that the arrow can cover
// cycles * 100 to get distance (as you can see, 100 cycles = 3000 max distance)
private constant function GetCycles takes nothing returns integer
return 100
endfunction
// Arrow's detection range
private constant function GetArrowDetect takes nothing returns real
return 120.
endfunction
// Returns the stun's max duration
private constant function GetStunMaxDuration takes integer level returns integer
return 6 * level
endfunction
// Returns the units that can be affected by the spell
private function GetAffectedUnits takes unit caster, unit target returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_STRUCTURE)) and GetUnitAbilityLevel(target, 'Aloc') == 0
endfunction
//==========================================================================================//
// CONFIGURABLE's END //
//==========================================================================================//
globals
private constant group G = bj_lastCreatedGroup
endglobals
private struct Spell extends array
private static integer instanceCount = 0
private static thistype recycle = 0
private thistype recycleNext
private unit caster // Caster
private unit target // Target of stun
private unit dummy // Arrow
private integer level // Level of spell
private real a // Angle for arrow
private integer cycles // Distance of arrow -> To get the distance: cycles * 30 (30 is how much the arrow's dummy moves in every period)
private method destroy takes nothing returns nothing
set this.caster = null
set this.target = null
set this.level = 0
set this.a = 0
set this.cycles = 0
set recycleNext = recycle
set recycle = this
endmethod
static method stun takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call UnitRemoveAbility(this.target, STUN_BUFF)
call PauseTimer(GetExpiredTimer())
call DestroyTimer(GetExpiredTimer())
call this.destroy()
endmethod
private method periodic takes nothing returns nothing
local unit j
local unit dummy
local timer t
// Moving the arrow
call SetUnitPosition(this.dummy, GetUnitX(this.dummy) + 40. * Cos(this.a), GetUnitY(this.dummy) + 40. * Sin(this.a))
set this.cycles = this.cycles + 1
// Checking if the arrow has already covered the max distance
if this.cycles < GetCycles() then
call GroupEnumUnitsInRange(G, GetUnitX(this.dummy), GetUnitY(this.dummy), GetArrowDetect(), null)
loop
set j = FirstOfGroup(G)
exitwhen j == null
call GroupRemoveUnit(G, j)
// If we've a target
if GetAffectedUnits(this.caster, j) then
// The stun can only stay for STUN_M_DURATION seconds
if (((this.cycles * 30) / 150) * 0.5) > GetStunMaxDuration(this.level) then
set this.cycles = GetCycles() / 2
endif
// Invis check...
if IsUnitInvisible(j, GetOwningPlayer(j)) then
static if AFFECT_INVIS then
// If the unit is invisible we will only put
// the stun if AFFECT_INVIS is true
set dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMY, GetUnitX(j), GetUnitY(j), 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1.)
call UnitAddAbility(dummy, STUN_RAWCODE)
call IssueTargetOrder(dummy, "thunderbolt", j)
endif
else
set dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMY, GetUnitX(j), GetUnitY(j), 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1.)
call UnitAddAbility(dummy, STUN_RAWCODE)
call IssueTargetOrder(dummy, "thunderbolt", j)
endif
call KillUnit(this.dummy)
set this.dummy = null
set this.target = j
// Making damage...
call UnitDamageTarget(this.caster, j, GetDamage(this.level), false, true, ATK_TYPE, DMG_TYPE, null)
// Timer of stun...
set t = NewTimer()
call SetTimerData(t, this)
call TimerStart(t, ((this.cycles * 30) / 150) * 0.5, false, function thistype.stun)
set t = null
call this.stopPeriodic()
call GroupClear(G)
exitwhen true
endif
endloop
else
call KillUnit(this.dummy)
set this.dummy = null
call this.stopPeriodic()
call this.destroy()
endif
set dummy = null
set j = null
endmethod
implement T32x
private static method create takes nothing returns thistype
local thistype this
if (recycle == 0) then
set instanceCount = instanceCount + 1
set this = instanceCount
else
set this = recycle
set recycle = recycle.recycleNext
endif
set this.caster = GetTriggerUnit()
set this.level = GetUnitAbilityLevel(this.caster, SPELL_RAWCODE)
set this.a = Atan2(GetSpellTargetY() - GetUnitY(this.caster), GetSpellTargetX() - GetUnitX(this.caster))
set this.dummy = CreateUnit(GetTriggerPlayer(), DUMMY_ARROW, GetUnitX(this.caster), GetUnitY(this.caster), this.a * bj_RADTODEG)
call this.startPeriodic()
return this
endmethod
private static method condition takes nothing returns boolean
if (GetSpellAbilityId() == SPELL_RAWCODE) then
call thistype.create()
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local unit u
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.condition))
// Preloading ability and dummy unit
set u = CreateUnit(Player(15), DUMMY, 0, 0, 0)
call UnitAddAbility(u, STUN_RAWCODE)
call RemoveUnit(u)
set u = null
endmethod
endstruct
endlibrary
//TESH.scrollpos=9
//TESH.alwaysfold=0
library MoveSpeedXGUI /* v1.0.0.1
*************************************************************************************
*
* This library allows you to set unit movement speeds beyond 522 without bugs.
* This is an extension of the library MoveSpeedX, but is formatted for GUI use.
*
************************************************************************************
*
* SETTINGS
*/
globals
private constant real PERIOD = 0.02390
// This is the period on which all units will be run.
// If you lower this value, movement bonuses will be smoother,
// but will require more processing power (lag more).
// Also, the lower this is, the higher the move speed can be
// before it starts bugging on waypoints. The lowest valid
// period is 0.00125. A period of 0.00625 is very robust.
private constant real MARGIN = 0.01
// This is the margin of approximation when comparing reals.
// You will most likely not need to change this.
endglobals
/*
************************************************************************************
*
* Functions
*
* function GetUnitMoveSpeedX takes unit whichUnit returns real
* - Returns a unit movement speed. The GUI function will
* - not return the correct value. This function will always
* - return the correct value regardless of whether the unit
* - has a movement speed beyond 522.
*
************************************************************************************
*
* REQUIREMENTS
*
* 1. JassNewGen Pack v5d
* 2. JassHelper 0.A.2.B
*
* HOW TO IMPLEMENT
*
* 1. Copy the trigger MoveSpeedXGUI.
* 2. Paste it into your map.
* 3. Open "Advanced -> Gameplay Constants".
* 4. Checkmark "Use Custom Gameplay Constants".
* 5. Find the field, "Movement - Unit Speed - Maximum", change
* that to 522.
* 6. Find the field, "Movement - Unit Speed - Minimum", hold
* shift and click, and change it to 0.
* 7. Read HOW TO USE.
*
************************************************************************************
*
* HOW TO USE
*
* This system will automatically work by itself. You can use the
* normal GUI function for modifying unit movement speeds. Simply
* use "Unit - Set Movement Speed", input whatever value you want,
* and you are good to go! It will handle values beyond 522 by itself.
*
* HOWEVER, the GUI function will not return correct values if a unit
* has a movement speed greater than 522. To fix this, use the function
* GetUnitMoveSpeedX to return the correct value. A sample is given in
* the trigger "Speed Change" in the test map.
*
************************************************************************************
*
* NOTES
*
* The unit may move randomly about one point before finally stopping. If
* this occurs, try changing PERIOD or reduce the unit movement speed.
*
* This also will not factor in bonuses.
*
************************************************************************************/
private function ApproxEqual takes real A, real B returns boolean
return (A >= (B - MARGIN)) and (A <= (B + MARGIN))
endfunction
private module M
private static integer ic = 0
private static integer ir = 0
static hashtable hash = InitHashtable()
thistype next
thistype prev
unit curr
real speed
real x
real y
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
set .prev = ir
set ir = this
call RemoveSavedInteger(hash, 0, GetHandleId(.curr))
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next // first instance in list
local real nx = 0 // the x-coordinate after tick
local real ny = 0 // the y-coordinate after tick
local real dx = 0 // distance between new-x and old-x
local real dy = 0 // distance between new-y and old-y
local real d = 0 // distance between new point and old point
local unit u // unit being affected
loop
exitwhen this == 0
set u = .curr
set nx = GetUnitX(u)
set ny = GetUnitY(u)
if (not IsUnitPaused(u)) and GetUnitAbilityLevel(u, 'BSTN') == 0 and GetUnitAbilityLevel(u, 'BPSE') == 0 then
if not ApproxEqual(nx, .x) or not ApproxEqual(ny, .y) then
set dx = nx - .x
set dy = ny - .y
set d = SquareRoot(dx * dx + dy * dy)
set .x = nx + dx / d * .speed
set .y = ny + dy / d * .speed
call SetUnitX(u, .x)
call SetUnitY(u, .y)
endif
endif
set this = this.next
endloop
set u = null
endmethod
static method create takes unit whichUnit, real newSpeed returns thistype
local thistype this = ir
if this == 0 then
set ic = ic + 1
set this = ic
else
set ir = .prev
endif
set this.next = thistype(0).next
set thistype(0).next.prev = this
set thistype(0).next = this
set this.prev = 0
set this.curr = whichUnit
set this.speed = (newSpeed - 522) * PERIOD
set this.x = GetUnitX(whichUnit)
set this.y = GetUnitY(whichUnit)
call SaveInteger(hash, 0, GetHandleId(whichUnit), this)
return this
endmethod
static method update takes unit whichUnit, real newSpeed returns nothing
local thistype this = 0
if HaveSavedInteger(hash, 0, GetHandleId(whichUnit)) then
set this = LoadInteger(hash, 0, GetHandleId(whichUnit))
if newSpeed > 522 then
set this.speed = (newSpeed-522)*PERIOD
else
call this.destroy()
endif
elseif newSpeed > 522 then
call thistype.create(whichUnit, newSpeed)
endif
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(), PERIOD, true, function thistype.periodic)
endmethod
endmodule
private struct MoveSpeedStruct extends array
implement M
endstruct
function GetUnitMoveSpeedX takes unit whichUnit returns real
if HaveSavedInteger(MoveSpeedStruct.hash, 0, GetHandleId(whichUnit)) then
return (MoveSpeedStruct(LoadInteger(MoveSpeedStruct.hash, 0, GetHandleId(whichUnit))).speed/PERIOD)+522
endif
return GetUnitMoveSpeed(whichUnit)
endfunction
function SetUnitMoveSpeedX takes unit whichUnit, real newSpeed returns nothing
call MoveSpeedStruct.update(whichUnit, newSpeed)
endfunction
hook SetUnitMoveSpeed SetUnitMoveSpeedX
endlibrary