Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
"After downloading the RPGminimap, I thought I could use ALICE to
improve my missile system since it’s now installed anyway. Best thing I could do,
now I'm having tremendous fun applying ALICE capabilities everywhere!" —Xthreo
"This is now part of the Lua Bible." —Wrda
I am putting up all of my resources for free. If you like my work, please consider buying me a coffee!
Overview
Features
Installation
Tutorial & Documentation
Change Log
Credits
Overview
A Limitless Interaction Caller Engine (A.L.I.C.E) takes Warcraft III mapmaking to the next level by providing a high performance framework for different types of systems, such as physics and missile systems, and allowing them to be interconnected easily. ALICE is the Unreal-engine of Warcraft 3 - it has a bit of a learning curve, but once you've mastered it, you will be able to create any kind of complex system and add new functionality with just a few lines of code.
So, let's enter wonderland!
Heads-Up
Do not get intimidated by the complexity of this resource! While there are a multitude of API functions, you can build your first systems with just a few lines of code, like it is done in the examples of the Test Map. ALICE is also packaged with ready-to use libraries that provide a solid starting point for physics and missile systems.
Also, check out these stand-alone systems built with ALICE:
ALICE attaches so-called actors to Warcraft 3 objects or to your own objects in order to mediate interactions between them and other nearby objects. Actors carry identifiers and function interfaces determining the types of actors they interact with. You fully define the functions that are called, so these interactions can be anything from collision detection, to healing auras, or gravitational pulls. ALICE's job is only to take care of the control structure around your code.
While in other systems, you would have to dig yourself into the internals to add new functionalities to the objects of those systems, with ALICE you can do everything externally. ALICE makes full use of Lua's dynamically-typed nature, allowing you full flexibility with the objects you pass into it.
The main task when working with ALICE is creating actors, its main internal object, and their interaction functions. An actor's properties are fully encoded in those tables and functions. For example, here is the code used to create the reflective barrier that Jaina uses in the showcase video:
Lua:
--The callback function on collision with the barrier:
local function ReflectProjectile(barrier, shell, x, y, z, normalSpeed, __, __)
local phi, theta = ALICE_PairGetAngle3D()
local shieldEffect = AddSpecialEffect("Abilities\\Spells\\Undead\\ReplenishMana\\ReplenishManaCasterOverhead.mdl", x, y)
BlzSetSpecialEffectZ(shieldEffect, z)
BlzSetSpecialEffectYaw(shieldEffect, phi)
BlzSetSpecialEffectPitch(shieldEffect, -theta + bj_PI/2)
BlzSetSpecialEffectAlpha(shieldEffect, math.min(128, normalSpeed // 2))
BlzSetSpecialEffectScale(shieldEffect, 1.2)
DestroyEffect(shieldEffect)
end
--This is the definition of the reflective barrier object:
ReflectiveBarrier = {
--These fields are used by ALICE itself.
identifier = "reflectiveBarrier",
interactions = {
projectile = CAT_GizmoCollisionCheck3D --This is a collision detection function included in the collisions library.
},
radius = 200,
bindToOrder = "starfall",
zOffset = 50,
--These are additional fields used by the collisions library.
onGizmoCollision = {
shell = CAT_GizmoBounce3D,
other = CAT_GizmoDevour3D
},
onGizmoCallback = ReflectProjectile,
mass = 500,
collisionRadius = 160,
elasticity = 0.2,
}
ReflectiveBarrier.__index = ReflectiveBarrier
local function CreateReflectiveBarrier()
if GetSpellAbilityId() ~= FourCC("Ashi") then
return
end
local barrier = {}
setmetatable(barrier, ReflectiveBarrier)
local u = GetSpellAbilityUnit()
barrier.anchor = u --The barrier becomes attached to Jaina.
--This creates the actor for the ReflectiveBarrier table.
ALICE_Create(barrier)
end
Motivation
Although the ALICE core was based on my particle system, my motivation for the development of this system was to create A.I. with it, where the interactions would be objects on the map sending data and being evaluated by a bot, who uses this data to calculate its next action. The endless possibilities of Lua soon got me hooked and I started expanding the system and now it is far beyond the scope I had originally intended (I can't stop, please send help!).
I realized that this engine could be used to solve one of the problems that has bugged me in Warcraft 3 for a long time - that you have to treat your own objects (such as particles, missiles) inherently differently from Warcraft 3 objects, since there is no native API, no enum functions, for them. Lua's dynamically typed nature provided an opportunity to remove this separation.
Unfortunately, the heavy reliance on dynamic typing means that ALICE cannot be translated into JASS. However, an interface for Lua-GUI would be a possible in the future.
Features
Feature
Description
Automatic Type Detection
ALICE supports any type of object passed into it, although most of its features specifically support units, destructables, items, and gizmos. Gizmos are defined as tables with coordinate fields and a special effect (missiles and such). ALICE automatically detects the types passed into it and uses the correct functions to retrieve their positions and other properties.
Debug API
ALICE features an extensive Debug API. By typing "downtherabbithole", you can enable debug mode and select objects to get a tooltip that displays information about the actors attached to it. While selected, all interactions are shown by a green lightning effect whenever they are evaluated. You can also use hotkeys to pause the cycle or go step-by-step. You can combine the ALICE debug mode with Eikonium's Debug Utils.
High performance
Unlike most other systems, ALICE's core avoids using enum functions entirely. Most missile systems, for example, would search for collisions with units by calling GroupEnumUnitsInRange repeatedly. But calling natives is slow and the process of calling a function repeatedly, even if there is no unit close to the missile, is inefficient itself.
Think of it this way: Calling GroupEnumUnitsInRange on every iteration is like having the most advanced GPS system there is installed in your car, but then determining the time until arrival by having a kid on the back-seat ask "Are we there yet?" over and over again.
ALICE instead divides the map into a grid of cells (spatial hashing), checks interaction ranges with other objects individually, and uses variable interaction intervals to drastically improve performance.
Enum functions
While the native functions only allow enumeration of units, destructables, and items, ALICE's Enum API allows for efficient enumeration of any type of object. Although the enum functions are significantly slower than their native counterparts, the fact that they compile the objects into a Lua table instead of a group almost cancels out the speed difference, and even makes them faster in some situations. It also makes those functions much easier to work with.
Self-interactions and Callbacks
All restaurants serve water. Sometimes, we just want to execute code on a single object. ALICE allows for this with its self-interaction and callback APIs. A self-interaction is a periodic function that only affects an object itself.
Async mode
Since ALICE is nothing but a giant table rearrangement machine, it can be run completely asynchronously (as long as the code executed with it is async-safe).
Extensive documentation
All API functions are fully documented and properly explained. In addition, there are manuals explaining both the basics as well as more advanced concepts.
Support from me
If you can't figure out how something works or you encounter a bug, leave me a note and I will try my best to fix it.
Installation
Copy the ALICE Config and Script section into your map. The rest of the ALICE files are documentation and it is up to you if you want to import them or not.
Next, make sure you have all the requirements imported. These are:
This handy little library allows ALICE to determine the correct types of the objects passed into it. Since it caches the result, it is fast and efficient.
Highly recommended if you want to do any kind of 3D interactions with ALICE as it improves performance and ensures synchronicity in multiplayer. Otherwise not needed.
CustomTooltip.toc
CustomTooltip.fdf
These are the frame definition files for the actor tooltips in debug mode. Included in the test maps.
Complementary ALICE Templates
CATs are templates for various systems, mostly focused on missiles and physics. They are not required; simply import the ones that you might want to work with. The CATs are:
CAT
Description
Objects
Requirement for all physics-related CATs. Includes the config and a collection of helper functions.
Gizmos, Missiles, Collisions,
Ballistics, Forces
These libraries contain various functions that you can use in the definitions of your objects.
Mice
Creates an actor for each player's mouse cursor.
Cameras
Creates an actor for each player's camera.
Rects
Contains helper function for the creation of actors from rects.
Tutorial & Documentation
Basics
Advanced
Debugging
Getting Started
Callbacks
Enumeration
Actors
Self-Interactions
Interactions
Flags
Getting Started
After installing ALICE and its requirements, go to the config and change the MAP_CREATORS field to your name. Preplace some units, trees, and/or items and launch your map. Type "downtherabbithole" to enable debug mode. Now you can click on any object while holding Ctrl to view the actors attached to it. A tooltip with all the relevant information should pop up when you do. If it doesn't, make sure you have .fdf and .toc files imported correctly.
In this tutorial, we will start with the ALICE callback API and its return value syntax, then introduce actors as a way to instantiate callbacks and enumerate objects, before finally diving deep into spatial hashing and interaction functions.
One of the primary strengths of ALICE is that every system can easily be customized and expanded. But you can also just work with the pre-packaged CAT libraries. However, a rudimentary understanding of the fields you're setting in your tables and why you're setting them might still be advantageous.
The CAT libraries are not meant to be fully functional systems, but rather offer functions and snippets you can use or pull code from to stitch together your own systems. Their documentation of the CATs is included in their script files.
Callback API
The callback API is ALICE's most basic feature and shares a lot of functionality with Eikonium's TimerQueue, although the syntax is slightly different. We can queue up any callback with ALICE_CallDelayed:
Lua:
ALICE_CallDelayed(print, 3.5, "Hello World!")
The first argument specifies the function called, and the second argument the delay. Each additional argument gets passed into the callback function, so here we're simply printing "Hello World!".
ALICE_CallPeriodic and ALICE_CallRepeated are used in cases where we want to call a function multiple times. These functions use ALICE's return value syntax, which is shared by all other callback functions. The return value of the function tells ALICE the delay before the next execution should be queued up:
Lua:
local function PrintNumbers(data)
data.count = data.count + 1
print("The current number is " .. data.count ". The next number will be printed in " .. data.count .. " seconds.")
return data.count
end
ALICE_CallPeriodic(PrintNumbers, 1, {count = 0})
The second argument again specifies the delay, in this case of the first execution. In all callback functions, the return value can be omitted. This causes the callback to be executed every step (set by ALICE_Config.MIN_INTERVAL).
No return value is not the same as a return value of ALICE_Config.MIN_INTERVAL. The former case optimizes the function directly as one that is executed every step, avoiding the overhead of managing callback queues. However, once a function has been put into the every-step loop, it will be stuck there permanently, so you must not mix return-value and no-return-value in different branches of your function.
All callback functions return a caller object, which can be used to disable the associated callback with ALICE_DisableCallback. You can also disable callbacks from within the callback function by passing no argument.
Lua:
local function CreateUnits()
CreateUnit(Player(0), FourCC "hfoo", 0, 0, 0)
if ALICE_TimeElapsed > 600 then
ALICE_DisableCallback()
end
return 5
end
createCaller = ALICE_CallPeriodic(CreateUnits)
function DisableCreateUnits()
ALICE_DisableCallback(createCaller)
end
A very useful design pattern for creating periodic callbacks that should only be active once is:
Callbacks, like everything in ALICE, runs on a single periodic timer. Because no timers are created or started, callbacks can safely be queued up async as long as the callback function is async-safe. In the following example, we use ALICE_CallRepeated, which is a simple variation of ALICE_CallPeriodic that will run a set number of times and passes the current iteration as the first parameter:
Lua:
local function FadeOutFrame(counter, whichFrame)
BlzFrameSetAlpha(whichFrame, 255 - counter)
if counter == 255 then
BlzFrameSetVisible(whichFrame, false)
end
end
local function ExpireFrame(whichFrame)
if GetLocalPlayer() == Player(0) then
fadeCaller = ALICE_CallRepeated(FadeOutFrame, nil, 255, whichFrame)
end
end
local function ShowFrame(whichPlayer, whichFrame)
if GetLocalPlayer() == whichPlayer then
BlzFrameSetVisible(whichFrame, true)
BlzFrameSetAlpha(whichFrame, 255)
ALICE_DisableCallback(fadeCaller) --Make sure existing fade-out callbacks are stopped so they don't immediately hide the frame again.
end
end
Enumeration
ALICE provides several enumerator functions that work similar to the native group API. The simplest enum function is:
Lua:
ALICE_EnumObjects(identifier, condition, ...)
The identifier is a property given to ALICE's main internal objects, the actors, which it uses to filter the objects that should be enumerated. By default, ALICE attaches actors to all widgets, giving them the following identifiers:
Units
Unit actors get the "unit" identifier, the fourCC code, as well as any classifications added to the ALICE_Config.UNIT_ADDED_CLASSIFICATIONS table. Upon death, the actor is not destroyed, but its "unit" identifier is swapped with "corpse". Because of this, if you want to capture only alive units, always include the "unit" keyword.
Destructables
Destructable actors get the "destructable" identifier and the fourCC code.
Items
Item actors get the "item" identifier and the fourCC code.
For example, this code snippet returns a table containing all Footmen owned by Red:
Lua:
local function IsOwnedByPlayer(whichUnit, whichPlayer)
return GetOwningPlayer(whichUnit) == whichPlayer
end
ALICE_EnumObjects({"hfoo", "unit", MATCHING_TYPE_ALL}, IsOwnedByPlayer, Player(0))
In addition to being more convenient to use than the native group API, ALICE's enumerators are far more flexible, allowing you to not only enumerate widgets, but also your own custom objects. To do so, we need to register those objects with ALICE by attaching actors to them.
An actor is a class that attaches itself to any type of object and acts as the mediator for enumerator functions and for any interaction with that object. Actors carry a table containing one or more strings called identifiers.
To create an actor, do ALICE_Create(host, identifier, interactions, flags).
host
Any type of object which the actor is supposed to represent and be attached to. While there are no restrictions as to which objects you can attach actors to, in 99.99% of cases, they will be either widgets or Lua tables. You can create multiple actors for one host.
identifier
A string or string sequence that can be used in a filter to identify different types of actors. Examples: "hero", "missile", or {"unit", "hero", "human", "Hpal"}. It is recommended to always use camelCase formatting for identifiers.
interactions
A table that specifies the functions that are called when this object interacts with other objects. More on this in Interactions.
flags
Flags are an optional fourth input argument discussed under Flags.
Creating an actor for a table
When creating an actor for a table, you have the option to omit all input arguments except the first. If you do, the identifier, interactions table, and all flags will be retrieved directly from the table fields and copied over to the actor.
In addition, there are special fields that ALICE may recognize in the host table. For the coordinates of the object, you must use "x", "y", and "z". The z-coordinates are optional. "visual" is the visual representation of the object (most likely a special effect, but can also be a dummy unit or image). "owner" is the owner, which can either be a player or a converted player id. You may use other names for those properties, but they will be ignored by ALICE if you do. Lastly, the host table may hold any other fields that you can read and manipulate freely in your callback functions. Example:
There are two functions to destroy actors, ALICE_Destroy and ALICE_Kill. ALICE_Destroy simply removes the actor, but leaves its host unharmed. ALICE_Kill nukes the entire object; it destroys all actors attached to it, and then invokes the appropriate destroy function for the host. If the host is a table, it will attempt to call the host:destroy method if it exists. Otherwise, it will attempt to destroy the host's visual, if it is set.
All copied fields must be set before ALICE_Create is called. Changing fields afterwards has no effect. Instead, you should use the appropriate API function to change an actor's properties after creation.
Self-Interactions
A self-interaction is a special type of periodic callback that exists tied to an object. Examples could be a function that restores health to a unit over time or moves a missile's coordinates and its special effect each step. Self-interactions can be added and removed with the ALICE_AddSelfInteraction and ALICE_RemoveSelfInteraction functions, but can also added directly during the creation of an actor. The host is always passed into the self-interaction function. Self-interactions do not allow additional parameters to be passed. Example:
Lua:
local MoveMissile(missile)
missile.x = missile.x + missile.vx
missile.y = missile.y + missile.vy
missile.z = missile.z + missile.vz
BlzSetSpecialEffectPosition(missile.visual, missile.x, missile.y, missile.z)
end
local missile = {
x = 0,
y = 0,
z = 0,
vx = 10,
vy = 10,
vz = 0,
visual = AddSpecialEffect("Missile.mdx", 0, 0),
selfInteractions = MoveMissile
}
Here, we have created a simple missile represented by a special effect that moves every step in northeast direction.
You can declare multiple self-interaction functions by passing a table:
Lua:
selfInteractions = {
MoveMissile,
OrientMissile
}
Interactions
Now that we have learned what actors are and how callbacks work, we can finally dive into ALICE's spatial hashing system and two-way interactions.
Actors can carry function interfaces called interaction tables, which assign an interaction function to the identifiers of other actors they are supposed to interact with. When two actors come into interaction range, both actors check whether the identifiers of the other are contained within their interaction table, and if so, they form a pair. For example, an actor attached to a missile might pair with an actor attached to a unit to check for collision.
Once a pair has been created, the two actors will continue to interact until one of them gets destroyed, they leave their interaction range, or the pair is manually disabled. This works similarly to the self-interaction functions and uses the same return value syntax to determine the interaction intervals. Now, two arguments are passed into the interaction function; the interacting objects, with the one carrying the interaction being passed as the first argument.
Example:
Lua:
local function Expire(obelisk)
obelisk.time = obelisk.time - ALICE_Config.MIN_INTERVAL
if obelisk.time <= 0 then
ALICE_Kill(obelisk)
end
end
local function HealingAura(obelisk, unit)
local dist = ALICE_PairGetDistance2D()
if dist < 500 then
SetUnitState(unit, UNIT_STATE_LIFE, GetUnitState(unit, UNIT_STATE_LIFE) + 5)
end
return 0.5
end
local obelisk = {
x = GetSpellTargetX(),
y = GetSpellTargetY(),
owner = GetOwningPlayer(GetSpellAbilityUnit()),
visual = AddSpecialEffect(modelPath, GetSpellTargetX(), GetSpellTargetY()),
identifier = "obelisk",
interactions = {unit = HealingAura},
selfInteractions = Expire
radius = 500,
time = 10
}
ALICE_Create(obelisk)
Here, we have created an obelisk at a spell's target position represented by a special effect. It lasts for 10 seconds and affects nearby units by healing them for 5 health every 0.5 seconds if they are in a range of 500.
You can specify different interaction functions for different types of objects. For example:
Lua:
interactions = {milk = Drink, apple = Eat}
This will use the Drink function for all actors with the "milk" identifier and the Eat function for all actors with the "apple" identifier. A key can itself be a table, listing all the identifiers an object must have for a pairing to occur with that function. Example:
You can overwrite interaction functions when an additional identifier is present, such as {steak = Eat, [{"steak", "raw"}] = Grill}. However, you have to make sure that the interaction function is always unambiguous or ALICE will throw an error.
Self-interactions can also be declared in the interactions table:
Some actors may be passive and only receive pairs, some only initiate pairs, and some actors may do both, all dependent on the properties you assign to them. However, in a pair, only one actor may initiate. Two-way pairs are not possible. The initiating actor is refered to as the "male actor" and the receiving actor the "female actor".
Within all ALICE callback functions, you have access to the Pair API. These contain various utility functions that act on the current pair. Most of them also work for self-interactions. In fact, self-interactions are simply pairs in which the single host is passed as both the first and second argument.
Spatial Hashing
To increase performance, ALICE divides the map into a grid of cells. The cells track where the different actors are on the map. Each actor is considered to be in a cell if its box intersects with it. Two actors can only interact if they share at least one cell. They will start interacting as soon as one enters a cell the other is in already.
How many cells an actor is in is determined by its radius. The default radius of actors can be set in the config, but it can also be overwritten during actor creation. You can view the spatial hashing and the cells an actor is in by activating debug mode and selecting it (Ctrl + Left-Click).
Going back to our obelisk example, the actor's extents of the obelisk must be at least 500 from center to edge, or otherwise it would not be able to interact with units at that distance to mediate the healing aura interaction. This is where the radius flag comes in. We set the radius to 500, so that the two actors start interacting as soon as the unit is within the healing range. For moving objects, some additional leeway is recommended.
Note that this range check is inaccurate and only done to prune far-away objects and increase performance. You still have to check the exact distance within the interaction function.
Flags
Flags are a table that contains additional parameters that allow you to control the behavior of actors much more than what is possible with just the standard input arguments.
radius
Overwrites the default actor radius. Discussed in Interactions.
anchor
Overwrites the host as the source of the actor coordinates with the object provided in anchor. This flag is incredibly useful if you want to pass as the host a data table attached to a unit or other type of object. Then you can anchor the actor to the unit directly and omit coordinate fields in the table.
Setting an anchor also prevents actors anchored to the same object from pairing.
Advanced Flags
These flags are used for optimization or to solve very specific issues and are therefore more niche.
selfInteractions
An alternative way to add selfInteractions to an object. Use either this flag or the self key in the interactions table.
bindToBuff / bindToOrder
Automatically destroy the actor when the host or anchor unit no longer has the buff with the bindToBuff string or when its current order is no longer the string bindToOrder.
isStationary
Disables cell checks for this object if it is stationary and cannot move to improve performance. Automatically enabled for destructables.
onActorDestroy
A function that is executed when the actor is destroyed. Passes the host as the argument.
zOffset
Moves the z-position of the object relative to its host or anchor. This modifies the behavior of ALICE_PairGetDistance3D() and similar functions.
cellCheckInterval
The cells an object is in is not updated instantly. Instead, ALICE checks periodically if the object has left a cell and/or has entered a cell. A fast-moving object must be checked more often than a slow-moving object, so with cellCheckInterval you can overwrite the default value that is set in the config.
persistOnDeath
Do not destroy the actor automatically when the unit that is its host or anchor dies. This is useful for actors attached to units that should be reactivated when that unit is revived. Only has an effect if UNITS_LEAVE_BEHIND_CORPSES is enabled.
priority
Determines which actor takes priority over the other when deciding which interactionFunc is called in a case where it would be ambiguous otherwise. If both actors pair with the other, the interactionFunc of the one with the higher priority is called. The default priority is 0.
width / height
These flags are similar to the radius flag, but can be used to create a non-square actor.
hasInfiniteRange / isGlobal / isAnonymous
If any of these flags is set to true, the actor will interact over any distance.
hasInfiniteRange: The actor has a position on the map and can be enumerated by all ALICE_Enum functions.
isGlobal: The actor does not have a position and cannot be enumerated other than with ALICE_EnumObjects.
isAnonymous: The actor is global, but can additionally not be enumerated by any function and not pair with any other actor; i.e. only carry self-interactions.
Actors with the hasInfiniteRange flag are more performant than actors for which the radius has been set extremely high. Global actors are even more performant, and anonymous actors are yet again more performant than global actors.
isUnselectable
Actor cannot be selected in debug mode.
actorClass
A string that assigns a class to an actor. This allows ALICE to recycle actors and save time initializing various fields, shaving off a few microseconds during actor creation. All actors within a class must be created with identical parameters and not undergo any changes during their lifetime or those changes will carry over to the next object. This feature is only recommended for objects that are created with an extreme frequency.
Adding Interactions to Widgets
Managing Pair Data
Disabling Interactions
Accessing Actors
Asynchronous Code
Adding Interactions to Widgets
When you want to add interactions to widgets, there are multiple approaches. We can create a new actor on top of the already existing one. For example, here is an actor that represents a fire aura that damages all units in 500 range and is bound to a buff on the unit:
Lua:
function FireAuraDamage(source, target)
local dist = ALICE_PairGetDistance2D()
if dist < 500 then
UnitDamageTarget(source, target, DAMAGE_AMOUNT, DAMAGE_TYPE_MAGIC, ATTACK_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
end
return 0.5
end
function OnFireAuraBuff()
local identifier = "fireAura"
local interactions = {unit = FireAuraDamage}
local flags = {radius = 500, bindToBuff = "Bfir"}
ALICE_Create(GetSpellAbilityUnit(), identifier, interactions, flags)
end
If we want the aura to damage the unit itself, we can modify the interactions table by adding it as a self-interaction:
Lua:
local interactions = {unit = FireAuraDamage, self = FireAuraDamage}
Because the host is passed as both arguments into the interactionFunc in a self-interaction, the unit will damage itself.
As soon as we go to more complicated interactions, where we want to read additional data or store data for the unit, there is a more convenient method. This method involves creating an additional, intermediate table as the host and anchoring it to the unit:
Lua:
function OnFireAuraBuff()
local fireAura = {
identifier = "fireAura",
interactions = {unit = FireAuraDamage, self = FireAuraDamage},
anchor = GetSpellAbilityUnit(),
radius = 500,
bindToBuff = "Bfir"
}
ALICE_Create(fireAura)
end
The problem now is that the target in the self-interaction points towards the table, not the host unit. But we can fix this problem easily by modifying the interactionFunc:
Lua:
function FireAuraDamage(fireAura, target)
local dist = ALICE_PairGetDistance2D()
if dist < 500 then
UnitDamageTarget(ALICE_GetAnchor(fireAura), ALICE_GetAnchor(target), DAMAGE_AMOUNT, DAMAGE_TYPE_MAGIC, ATTACK_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
end
return 0.5
end
The ALICE_GetAnchor function returns the unit itself if target is a unit and the host unit if it is the fireAura table, so you're never trying to damage a table with the UnitDamageTarget function.
Managing Pair Data
ALICE offers various functions to store data for pairs and to execute code based on the current state of the pair.
Imagine we want to create an interaction where a lightning chain is created between two units whenever they are 500 or closer. When the units are in range, if it's the first time they are in range, we want to create the lightning, otherwise we want to move it. If they are not in range, we want to destroy the lightning if it exists.
For the creation of the lightning, we can use the ALICE_PairIsFirstContact function. We can store the data in a table that we get with ALICE_PairLoadData.
Lua:
function LightningChain(unitA, unitB)
local data = ALICE_PairLoadData()
if ALICE_PairGetDistance2D < 500 then
local x1, y1, x2, y2 = ALICE_PairGetCoordinates2D()
if ALICE_PairIsFirstContact() then
data.lightning = AddLightning("CLPB", false, x1, y1, x2, y2)
else
MoveLightning(data.lightning, x1, y1, x2, y2)
end
elseif data.lightning then
DestroyLightning(data.lightning)
data.lightning = nil
end
end
The problem with this implementation is that, when one of the units dies, or the units leave ALICE's interaction range, the function stops being executed and the DestroyLightning line is never reached, therefore leaking the lightning. To solve this issue, you can declare onDestroy functions. onDestroy functions are assigned to interaction functions.
ALICE_FuncSetOnDestroy is executed when the pair is destroyed. ALICE_FuncSetOnBreak is less strict than OnDestroy, as it is also executed when the pair leaves the interaction range. It would be better suited for this situation.
Lua:
function LightningChainOnBreak(__, __, data)
if data.lightning then
DestroyLightning(data.lightning)
data.lightning = nil
end
end
function LightningChain(unitA, unitB)
local data = ALICE_PairLoadData()
if ALICE_PairGetDistance2D < 500 then
local x1, y1, x2, y2 = ALICE_PairGetCoordinates2D()
if ALICE_PairIsFirstContact() then
data.lightning = AddLightning("CLPB", false, x1, y1, x2, y2)
else
MoveLightning(data.lightning, x1, y1, x2, y2)
end
elseif data.lightning then
DestroyLightning(data.lightning)
data.lightning = nil
end
end
ALICE_FuncSetOnBreak(LightningChain, LightningChainOnBreak)
The above implementation works better, but it still has a problem: The lightning is created only the first time the units come into range, but not the second time, because the ALICE_PairIsFirstContact function will return false. To solve this issue, we use the ALICE_PairReset function and replace ALICE_FuncSetOnBreak with ALICE_FuncSetOnReset. The ALICE_PairReset function resets ALICE_PairIsFirstContact and calls the OnReset function only if it was reset. Using the reset feature also simplifies our code.
Lua:
function LightningChainOnReset(__, __, data)
DestroyLightning(data.lightning)
end
function LightningChain(unitA, unitB)
local data = ALICE_PairLoadData()
if ALICE_PairGetDistance2D < 500 then
local x1, y1, x2, y2 = ALICE_PairGetCoordinates2D()
if ALICE_PairIsFirstContact() then
data.lightning = AddLightning("CLPB", false, x1, y1, x2, y2)
else
MoveLightning(data.lightning, x1, y1, x2, y2)
end
else
ALICE_PairReset()
end
end
ALICE_FuncSetOnReset(LightningChain, LightningChainOnReset)
This code structure can be used for almost all problems that involve creating effects/lightnings etc.
In a situation where we want to set up data tables or triggers etc. on creation of a pair, we do not use ALICE_PairIsFirstContact. Instead, we use the feature to add initializers to functions with ALICE_FuncSetInit. We can use ALICE_FuncSetOnDestroy to clean-up the data if necessary, but ALICE will take care of the data table clean-up in most situations.
Disabling Interactions
There are several ways to disable interactions between objects, each with their advantages and disadvantages.
Early Returns
The easiest method to disable an interaction is to put an early return into the interactionFunc based on some condition. All other methods are optimizations that are only relevant when performance is a major issue.
Here is a fire that deals damage only to enemy units:
Lua:
function FireDamage(fire, unit)
if not ALICE_PairIsEnemy() then
return 0.5
end
if ALICE_PairGetDistance2D() < 250 then
UnitDamageTarget(fire.source, unit, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
end
return 0.5
end
The advantage of this approach is that, if a unit is mind-controlled, the fire will start dealing damage, because the pair still exists.
Disabling Pairs
If there's no possibility for a unit to change owners or for players to change alliances, we can disable the pair completely with ALICE_PairDisable:
Lua:
function FireDamage(fire, unit)
if not ALICE_PairIsEnemy() then
ALICE_PairDisable()
return 0.5
end
if ALICE_PairGetDistance2D() < 250 then
UnitDamageTarget(fire.source, unit, 25, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
end
return 0.5
end
A further optimization could be to disable the pair on creation:
Lua:
ALICE_FuncSetInit(FireDamage, function()
if not ALICE_PairIsEnemy() then
ALICE_PairDisable()
end
end)
A disabled pair can be restored with the Pair Access function ALICE_Enable, but this function is difficult to use, because you have to provide the objects of each pair that needs to be restored.
Changing Identifiers
You can disable interactions by removing identifiers that are required for the interaction. For example, let's say the interaction table for the fire is:
Pausing Pairs
Like ALICE_PairDisable, ALICE_PairPause is called from within an interactionFunc and disables the pair until it is restored by another function. The differences are that pausing a pair preserves any data stored and unpausing is done on a per-function basis instead of a per-pair basis. Pausing should be viewed as a less permanent disable.
Let's say our fire has a strength that can change over time. It should only deal damage when it's strength is 10 or greater:
Lua:
function FireDamage(fire, unit)
if fire.strength < 10 then
ALICE_PairPause()
return 0.5
end
if not ALICE_PairIsEnemy() then
ALICE_PairDisable()
return 0.5
end
if ALICE_PairGetDistance2D() < 250 then
UnitDamageTarget(fire.source, unit, 5*(fire.strength - 10), false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
end
return 0.5
end
Within the function that controls the strength of the fire, we can unpause all paused pairs that use the FireDamage function when the fire's strength grows above the threshold:
Lua:
if oldStrength < 10 and newStrength >= 10 then
ALICE_Unpause(fire, FireDamage)
end
Accessing Actors
Whenever an actor is created, a reference is stored on both the host and the anchor. Example:
Every function that takes an object as an argument calls the same function to retrieve the correct actor. If the keyword parameter is omitted, the logic as to which actor is returned is as follows:
If the function is called from an interaction function or pair callback function, the actor that's part of the current pair is returned.
Otherwise, actors for which the object is the host, not the anchor, are prioritized.
The oldest actor is returned. For units, this might not always be the default unit actor, when there's a second actor created for it immediately after creation of the unit.
Unless you're calling an object function from an interaction function, omitting the keyword parameter should be discouraged, as it can easily lead to bugs as a system grows in complexity.
Asynchronous Code
ALICE can be used to run code asynchronously (in GetLocalPlayer() blocks), as long as the usual safety precautions are followed.
Do not create or destroy handles asynchronously.
Do not affect the game state asynchronously.
Do not use random functions asynchronously.
If these rules are followed, any function that is not a debug function is async-safe. For example, you can do
Lua:
if GetLocalPlayer() == whichPlayer then
ALICE_CallDelayed(function() BlzFrameSetVisible(whichFrame, false) end, 2.5)
end
instead of
Lua:
ALICE_CallDelayed(function()
if GetLocalPlayer() == whichPlayer then
BlzFrameSetVisible(whichFrame, false)
end
end, 2.5)
Even actors can be created and destroyed asynchronously as long as they do not carry any interactions that require synchronicity.
There is one niche exception: If you create an actor anchored to an item or destructable and that widget has had no actors before, it will create a death trigger and therefore cause a desnc.
Debug Mode
Troubleshooting
Investigation
Debug Mode
You can enable debug mode with the ALICE_Debug() function. You can also enable debug mode ingame by typing "downtherabbithole". While in debug mode, you can left-click on objects while holding Ctrl to select their actor and receive information about their attributes. In addition, their current cells and interactions will be visualized with lightning effects. You can combine debug mode with Eikonium's IngameConsole to execute ALICE debug functions. Actor tooltips requires "CustomTooltip.toc" and "CustomTooltip.fdf". Set your interactionFuncs to global or use ALICE_FuncSetName so that their names can get displayed!
In debug mode, you have access to these key shortcuts:
Key
Description
CTRL + Q
Cycle through the different actors anchored to the selected host.
CTRL + W
Lock selection so that clicking somewhere does not deselect the currently selected actor.
CTRL + T
Halt the cycle. Hold SHIFT to pause all units.
CTRL + R
Execute the next step of the cycle.
CTRL + G
Toggle printing the function names of all invoked interaction functions for the selected actor.
Troubleshooting
The two most common bugs that you will encounter are thread crashes and actors not interacting.
Thread Crashes
If your custom code crashes, because ALICE uses a single thread, the entire cycle will crash. You can toggle HALT_ON_FIRST_CRASH in the config to set whether a repair of the cycle should be attempted.
The most common cause of a crash is that a variable required for an interaction function is nil. This may be because you forgot to set a table field, you misspelled the key, you flipped the input arguments of the interactionFunc, or the actor erroneously pairs with an actor whose host is not supposed to hold that field or even has the wrong type.
The ALICE_RecognizeFields and ALICE_RequireFields functions are useful in helping you with the first two cases. You can also enable the Ingame Console, enable debug mode, and type ALICE_TrackVariables("varName") to check whether the fields that are supposed to be present are.
To check whether an actor was paired with another actor it is not supposed to, investigate the actors listed in the crash message. Enable the ingame console and type ALICE_Select(N), where N is the unique number of the actors listed.
Not destroying an actor when its host is destroyed will keep the pairs attached to that actor inside the cycle indefinitely. The pairs will continue to be evaluated, clogging up the system. Because the reference to the host is lost, this might also be the source of a crash. Actors attached to widgets will be cleaned up automatically, but for actors attached to tables, you have to do it manually. Always use ALICE_Kill to remove an object, so that you always destroy all actors attached to it. The object's .destroy method will be called by ALICE_Kill if it exists.
Actors not interacting
This can be due to actors not having the intended pairing behavior, or because of the radius of the actors being too small.
To check if the pairing behavior is correct, check the actor tooltip in debug mode.
If you select an actor, you will see the cells it is currently in. This will incidate its interaction range. If you set the radius of an actor too low or forget to change it from its default value, the actors will interact, but only at short range. The radius should be set to the maximum interaction distance plus some leeway. The leeway should be equal to the maximum distance the object can travel during two cell checks (CELL_CHECK_INTERVAL). Select the actor in debug mode to see the cells the system thinks it's currently in.
To check the state of a pair, select the actors in debug mode, then do ALICE_GetPairState(N1, N2), where N1 and N2 are the unique numbers of the actors. The pair state can give you additional information about what might be the issue.
Other Errors
C-Stack Overflow
This error occurs when you nil the coordinate fields of a table host without destroying the actor.
Investigation
Debug Utils is incredibly useful when it comes to finding bugs that result in thread crashes. But, because Warcraft III does not offer any debug tools worth mentioning, other, less severe bugs are often much harder to find. This is especially the case when the bug only occurs in a few corner-case scenarios. Often, the only out is to spam the screen with print messages until the bug occurs and then scour through the messages to find the crucial line.
ALICE improves the debugging experience by allowing you to track variables in the actor tooltips. Consider that our interactionFunc has the following structure:
Lua:
function InteractionFunc(objectA, objectB)
if not condition1 then
return
end
if not condition2 then
return
end
if not condition3 then
return
end
DoStuff(objectA, objectB)
end
Sometimes, one of the conditions fails and the DoStuff function isn't called when it should be. Instead of bombarding the function with print statements, we store debug variables in the object tables.
Lua:
function InteractionFunc(objectA, objectB)
objectA.failedCondition1 = nil
objectA.failedCondition2 = nil
objectA.failedCondition3 = nil
if not condition1 then
objectA.failedCondition1 = true
return
end
if not condition2 then
objectA.failedCondition2 = true
return
end
if not condition3 then
objectA.failedCondition3 = true
return
end
DoStuff(objectA, objectB)
end
We can now make these table fields get displayed in the actor tooltips by using ALICE_TrackVariables, either through the IngameConsole or by calling it from the Lua root.
As soon as the bug occurs, we halt the cycle by pressing Ctrl + T and select the actor that misbehaved. We can now go step-by-step by pressing Ctrl + R.
To make this method work for hosts that aren't tables, create global tables and store the value at the key of the object.
Added the ALICE_Teleport function, which instantly updates an object's position and works on stationary objects.
Fixed the math of the CAT_MovedArced function in situations where the launch-z and the target-z are different.
The ALICE_Kill function now uses an optional flag that, if set, removes a unit or destructable instead of killing it.
Units with Locust now get an actor if they're included with ALICE_IncludedTypes.
Version 2.8.4
Fixed a bug that prevented the cycle from being resumed after a crash.
Improved the annotations of flags in the ALICE_Create function.
Version 2.8.3
Selecting an actor in debug mode now requires Ctrl to be pressed.
Add the ALICE_NextStep function to the debug library.
Added the actorClass flag. Setting an actor class allows ALICE to recycle actors and save time initializing various fields, shaving off a few microseconds during actor creation. This is useful for objects with an extremely high creation frequency.
Version 2.8.2
ALICE_Create now returns the host so it can be chained.
The debug hotkeys can now be customized in the config.
Version 2.8
The ALICE error handling system is working again and does now print appropriate error messages for crashes caused by callback functions.
New global: ALICE_Where. Denotes where in the ALICE cycle we currently are.
Added the MultiPassThrough onCollision callback functions, which trigger every time the gizmo passes through an object.
Added the CAT_MoveSound function to the Effects CAT, which attaches a 3D sound to an object.
Fixed a bug that caused ALICE_OnCreationAddIdentifier to overwrite other identifier hooks that were created for the other identifiers of an object that has multiple.
Fixed an issue where onUnitEnter hooks would fire on units created by CreateUnit and similar functions, even if they did not receive a unit actor.
Fixed a bug that could rarely cause multiple API functions to crash the system by causing a double-deletion of pairs.
Version 2.7.3
Fixed a bug that caused a crash when setting an object that has global actors attached to it to stationary.
Fixed a bug that caused the zOffset flag to be ignored for table objects with no z-coordinates.
Fixed an issue where references to actors on objects would not immediately get removed when the actor was destroyed, leading to potential unexpected behavior.
Fixed a bug that caused a crash when attempting to return the cellCheckInterval flag of a stationary object.
Version 2.7.2
The ALICE_AddSelfInteraction function can now accept a data table that can be loaded with ALICE_PairLoadData.
The ALICE_SetInteractionFunc function now affects already established pairs.
Added minor optimizations.
Fixed a bug that could cause the ALICE_DisableCallback function to disable a different callback if the original one had already been disabled and recycled.
Fixed a bug that caused the ALICE_PairInterpolate function to sometimes crash the cycle when used in conjunction with the callback API.
Version 2.7
Added the ALICE_PairInterpolate function, which allows the interaction function to be called at a higher rate than the ALICE_Config.MIN_INTERVAL.
You can now define onDestroy functions for callbacks with ALICE_FuncSetOnDestroy. The onDestroy function will be executed after a callback using the specified function expires or when it is disabled.
Split the Objects CAT into the Interfaces, Units, and Data CATs.
All CAT config variables are now stored in the CAT_Data table.
Improved the included knockback system. Units being knocked against a wall will now continue to move into the direction in which they can still move. The unit's collision size is now taken into account. Items on the ground will no longer interrupt a knockback.
Added the CAT_GetUnitKnockbackSpeed and CAT_UnitEnableFriction functions.
All interaction functions in the CAT libraries are now annotated, listing all required and optional table fields for the respective function.
Fixed a bug that caused many API functions to not work in an onReset, onBreak, or onDestroy callback function of a self-interaction during actor destruction.
Purged all tab characters from documentation.
Version 2.6.3
Fixed a bug that caused a callback from ALICE_CallPeriodic or ALICE_CallRepeated to sometimes not be disableable.
Version 2.6.2
Updated MINACE to 2.6.2.
Added the isAnonymous actor flag. Anonymous actors cannot pair with other actors (only carry self-interactions), and cannot be enumerated, allowing all unnecessary overhead to be removed.
Removed the nil-argument warning in ALICE_DisableCallback, so that it is no longer necessary to check nil before disabling a callback.
Fixed a bug that caused the ALICE_DisableCallback function to crash the engine when used to disable the same callback multiple times.
Fixed a bug that caused the Objects CAT to crash the map on initialization when put above the ALICE script.
Version 2.6
Added the ALICE_DisableCallback function, which inherits the functionality of ALICE_DisablePeriodic, but can also interrupt callbacks queued up with ALICE_CallDelayed.
ALICE_DisablePeriodic is now deprecated.
Added the ALICE_PauseCallback function, which can pause or unpause a callback created by ALICE_CallDelayed, ALICE_CallPeriodic, or ALICE_CallRepeated.
Added the Walls CAT, which contains functions to detect and execute collisions of objects with vertical walls.
Added the Trees CAT, which adds the tree identifier to all trees.
Removed the UNRECOGNIZED_FIELD_WARNINGS config option and the ALICE_RecognizeFields function.
Removed the REQUIRED_FIELD_WARNINGS config option. An error message listing missing required fields in tables is now automatically generated on a thread crash. Missing required fields are now also listed in the actor tooltips.
With the ALICE_FuncRequireFields function, you can now define conditional required fields, i.e. fields that are only required if another field is also set.
You can now select an actor with ALICE_Select by identifier.
Fixed a bug in the Collisions 3D CAT with the special effect positioning on impact.
Version 2.5.4
Fixed a bug that caused the enum functions to sometimes enumerate objects twice.
Version 2.5.3
As GetUnitName and similar functions can return different results on different client languages when used on units with a default name, adding widget names to identifiers must now be enabled in the config, where there is a warning about possible desyncs.
Version 2.5.2
Updated the Cinematic Map to be compatible with the latest version.
Added the ALICE_FindIdentifier function, which returns the first entry in a list of identifiers for which an actor exists for a specified object.
selfInteractions is now a recognized flag that can be used as an alternative way to add self-interactions to an object.
Minor perfomance optimizations.
Fixed a typo that caused the ALICE_FindField function to not do anything (kekw).
Fixed a bug that caused the onCollision functions to sometimes crash when calculating the impact of a very fast object.
Fixed a bug that caused the CAT_AnimateShadow function to crash when the optional fields shadowX and shadowY were not set.
Fixed an issue that prevented the CAT_Knockback function form working on corpses.
Version 2.5
Unit actors are now available immediately after calling CreateUnit.
Revamped how units and items that are being hidden, loaded into transports, and/or picked up by units are handled. The actors are now preserved, but are suspended from all interactions until they reappear on the map.
Added the ALICE_FuncSetUnsuspendable function, which prevents an interaction from being disabled when an object is hidden, loaded into a transport, or picked up.
Creating a corpse with the CreateCorpse native will now create a corpse actor if UNITS_LEAVE_BEHIND_CORPSES is enabled.
The ALICE_SetAnchor, ALICE_SetRadius, and ALICE_SetCellCheckInterval functions have been removed and consolidated into the ALICE_SetFlag function, which can modify the values of most actor flags.
Added the ALICE_GetFlag function.
The persistOnDeath flag now only works on units.
Widget death event hooks are now executed before all actors are cleared.
Added an option to the config to overwrite ALICE's table recycling functions with external ones.
Updated the Cameras CAT, which used the removed ALICE_SetRadius function.
Fixed a bug that caused the destroy methods of the hosts of additional actors anchored to an object to not be executed upon the parent object's death.
Fixed a typo that caused some tables to prevent garbage collection.
Version 2.4.2
Fixed a bug that caused the periodic callback functions to not delay the first call by the correct amount if the specified delay exceeded the ALICE_Config.MAX_INTERVAL.
Version 2.4
Added MINACE, a stripped-down version of ALICE.
The fourCC codes of widgets are now added to the default actors' identifiers.
The ALICE_CallPeriodic function can now capture additional arguments and the interval is no longer limited by MAX_INTERVAL.
Added the ALICE_CallRepeated function, which works like ALICE_CallPeriodic, but executes the function a set number of times.
Added the ALICE_DisablePeriodic function, which should be used to disable periodic callbacks instead of ALICE_PairDisable.
Removed the ALICE_CallAt function.
Added the ALICE_GetPairState function for debug mode.
Removed the ALICE_SlowMotion function.
Removed the MAXIMUM_EVALUATION_TIME config parameter and the in-built freeze protection.
Changed the ALICE_Statistics function to print function names instead of actor identifiers.
The CAT_MoveArcedHoming function now recognizes the arcAngle field to allow missiles to not only arc vertically.
The CAT_UnitCollisionCheck function now recognizes the onlyTarget field, which excludes all units from the collision check other than the gizmo's target.
Instead of creating one death trigger for each actor, now at most one death trigger is created for each destructable and item.
Added the missing onDestructableEnter, onDestructableDestroy, onItemEnter, and onItemDestroy event hooks.
In debug mode, when clicking on a spot with multiple actors, you will now properly cycle through the different actors instead of repeatedly selecting the same one.
Fixed a bug that caused the CAT_MoveArced function to crash when the .arcAngle field was not set.
Fixed a bug that caused a single table passed as an additional argument into ALICE_CallDelayed to not be forwarded into the callback function.
Version 2.3.3
Fixed a bug that caused actors to not be destroyed properly when multiple actors are anchored to a widget (this time for real).
Version 2.3.2
Fixed a bug with the Forces CATs.
Added some missing annotations and fixed a find-and-replace mishap in the config annotations
Version 2.3
Asynchronous actors can now be paired with synchronous actors without risking a desync.
ALICE will now throw an error when the interaction function between two actors is ambiguous.
Shadowing interaction functions (such as {"unit" = DamageUnit, [{"unit", "hero"}] = DamageHero}) is now supported.
Improved config annotations. Config variables are not stored in the ALICE_Config table.
The destroyOnDeath flag has been replaced with persistOnDeath, which does the opposite. By default, all actors are now destroyed on a widget's death.
Added the width and height flags for actors, which can be used to create non-square actors.
The ALICE_Kill function can now also destroy unit- and image-based visuals.
Added the Rects CAT, which contains helper functions for creating actors from rects.
Added the onUnitChangeOwner option for ALICE_OnWidgetEvent.
Added the ALICE_SetInteraction function, which can modify the interaction table of an actor after creation.
Added the ALICE_FindField function, which returns the field in a table whose key matches an object's identifier.
Added the ALICE_GetAnchoredObject function, which returns objects anchored to the specified object.
Removed the ALICE_GetActor and ALICE_ForAllActorsDo functions.
ALICE_HasActor now has an optional flag to exclude actors only anchored to the object.
Added the ALICE_FuncSetName function so that interaction functions don't have to be globals in order for their names to be displayed in debug tooltips.
Pair Access functions can now access self-interactions by passing a function as the second argument.
The CAT_AnimateShadow function now uses images instead of special effects.
In the Collisions CAT, passing a table for onObjectDamage now works.
Fixed a bug that caused a destructable or item being removed or picked up to not destroy all actors if it has multiple.
Fixed a bug that caused ALICE_Kill to not destroy all actors on an object with multiple actors.
Fixed an issue that caused anchoring to an object that is itself anchored to another object to not work.
Version 2.2.3
Fixed a bug that caused item actors to have the wrong position when an item is dropped by a hero.
Fixed a bug that caused objects with the .hasInfiniteRange flag to be ignored by enum functions.
Fixed an issue that caused destructables to not receive an actor after being revived.
Removed AUTO_DEBUG_MODE and AUTO_SELECT config options. Instead, added ALICE_Debug function to enable debug mode.
Stationary flags are now always kept consistent among multiple actors attached to one object.
Version 2.2.2
Fixed a bug that caused the engine to crash when passing a non-widget handle as the host.
Fixed a bug that caused unpredictable behavior when changing the identifiers of global actors.
Classifications to unit actors can now be added individually.
Added the ALICE_OnWidgetEvent function. This function creates hooks on the widget event hooks (sup dawg) used by ALICE to create the default widget actors, allowing users to easily manage their own widget actors.
Added the ALICE_IsStationary function.
Added an optimization that caches the interaction function for two actor types, allowing it to be retrieved faster the next time two actors of the same types meet.
Version 2.2
Enabled asynchronous code execution. Pairs and actors can now be created asynchronously without risking a desync as long as certain rules are being followed.
Added the ALICE_EnumObjectsInLineSegment and ALICE_ForAllObjectsInLineSegmentDo functions.
Added the ALICE_GetClosestObject function.
Added the Cameras CAT, which creates an actor for each player's camera.
Various bug fixes.
Version 2.1.4
Accidently broke the Rockslide example in the test map in the last update. This has been fixed.
OnCreation hooks now always accept function arguments as advertised and now work properly in (hopefully) all situations.
ALICE_PairCooldown now returns the remaining cooldown instead of a boolean.
Added ALICE_FuncPauseOnStationary to declare a function that should always get paused when an object becomes stationary.
Removed the random delay option in ALICE_FuncSetDelay, as it is a possible cause of desync. Instead, added ALICE_FuncDistribute function to allow spreading out function calls over an interval.
Version 2.1.3
Fixed a bug that caused newly created objects to sometimes only start interacting when entering a new cell.
Fixed a bug that caused changing identifiers to sometimes crash the engine.
Version 2.1.2
Fixed the ALICE_PairCallDelayed function.
Added missing hooks for RemoveDestructable and RemoveItem that prevent actors from leaking.
All actors now get properly cleared on a widget death or removal, preventing possible leaks of additional actors.
ALICE_PairPause is now more reliable.
ALICE_PairCooldown now allows starting multiple cooldown.
The Matrix API has been removed and replaced with the Pair Access API, which is faster, more flexible, and less confusing.
Reduced memory requirement.
Version 2.1
The Units, Destructables, Items and Widgets CATs have been removed and incorporated into the main script.
Added unrecognized field warnings when using tables as hosts.
New hotkeys for debug mode: Halt, Resume, Next Step, Lock Selection, and Print Functions.
In debug mode, interaction visualization lightnings now last until at least the next step.
The interactionFunc table field has been renamed to interactions.
Functions are no longer permitted as the input for interactions. It now has to be a table. You can now list multiple identifiers in the key of an interactions table entry. The "other" keyword has been removed.
The pairsWith flag has been removed.
The cell size is now entered in the config, instead of the amount of cells.
Removed the ALICE_CreateFromClass function and incorporated its functionality into ALICE_Create.
Entities in the CATs have been renamed to gizmos.
Overhauled the pair data API. The ALICE_PairOnDestroy function has been removed and replaced by ALICE_FuncSetOnDestroy, ALICE_FuncSetOnBreak, and ALICE_FuncSetOnReset. ALICE_PairForget has been renamed to ALICE_PairReset.
Added the ALICE_TrackVariables function, which allows tracking of additional variables in actor tooltips.
Added the ALICE_PairPreciseInterval function to allow interaction intervals that are not integer multiples of the minimum interval.
Added the Callback API.
Added the ALICE_GetAnchor function.
Added the Modular API. It contains the OnCreation functions, which can inject properties into actors on creation and the ALICE_IncludeType and ALICE_ExcludeType functions, which overwrite the config for deciding which widgets receive actors.
In the Collisions CAT, the onWidgetDamage feature now works without declaring an onCollision function. onWidgetDamage can now be a function, not just a number. Added the option to disable friendly fire. The documentation has been improved.
Added the Mice CAT, which creates an actor for each player's mouse.
Return values in interaction functions are now optional. If the return value is omitted, ALICE will automatically put pairs using that function into the optimized EveryStep cycle.
Added API functions to add or remove self-interactions.
Added the Matrix API. These functions can be used to write data tables and share data between object pairs.
ALICE now uses the WidgetType library to infer the correct type of objects passed into it.
To improve performance with widgets, Math API functions now use cached values for object coordinates that get reset each cycle.
Freeze protection is now based on evaluation time instead of number of pairs.
ALICE_Benchmark can now be used to view the evaluation time of each cycle.
Added the HALT_ON_FIRST_CRASH option in the config.
Reorganized API into Core and Advanced API.
To-Do List
Fix bugs related to ballistic movement around cliffs.
Add a better knockback system.
Credits
Cinematic Showcase Map
Fighting Spirit for Spellbringers
Edited by Vinz
Bullet/Muzzle Flash Models by Vinz
Arcane Explosion Model by Suselishe
Starsphere by ILH
Battlecruiser and Carrier Model Imports by Daratrix
Plasma Blast Model by nGy
Missile Model by Vinz
Awarded Director's Cut due to how this resource expands beyond the concept of handling missiles: has an optimized algorithm for its own enumerations of units, destructibles, items, special effects and objects; has a great variety of API for one to add...
I started to replicate yours just for testing purposes. Now that I’m confortable with you system, I integrated mine in ALICE. Mine is mostly about the launch of the missile, since it’s camera based, I just set required values in my object table and let ALICE handle the interactions which then calls my impact functions
I started to replicate yours just for testing purposes. Now that I’m confortable with you system, I integrated mine in ALICE. Mine is mostly about the launch of the missile, since it’s camera based, I just set required values in my object table and let ALICE handle the interactions which then calls my impact functions
Very cool! If you're calling your own impact function, there's a helpful, little API function, which I don't know if you've found it yet. With ALICE_FindField you return a table entry matching an object's identifier. So, for example:
Very cool! If you're calling your own impact function, there's a helpful, little API function, which I don't know if you've found it yet. With ALICE_FindField you return a table entry matching an object's identifier. So, for example:
I’m also trying to find the best way to implement aoe damage on impact for certain missile. I am currently using the ALICE_enumObjectsInRange function, is this the best way?
Version 2.5 improves the way unit and item actors are handled. Previously, units loaded into a transport would just leak the actor, which would remain at the last known position. For items, all actors would be destroyed if an item is picked up by a hero. Now the actors will be preserved, but set to sleep until the item is dropped again. This will preserve minimap icons and waypoint markers you attach to items for the RPG Minimap system, for example.
Version 2.5
Unit actors are now available immediately after calling CreateUnit.
Revamped how units and items that are being hidden, loaded into transports, and/or picked up by units are handled. The actors are now preserved, but are suspended from all interactions until they reappear on the map.
Added the ALICE_FuncSetUnsuspendable function, which prevents an interaction from being disabled when an object is hidden, loaded into a transport, or picked up.
Creating a corpse with the CreateCorpse native will now create a corpse actor if UNITS_LEAVE_BEHIND_CORPSES is enabled.
The ALICE_SetAnchor, ALICE_SetRadius, and ALICE_SetCellCheckInterval functions have been removed and consolidated into the ALICE_SetFlag function, which can modify the values of most actor flags.
Added the ALICE_GetFlag function.
The persistOnDeath flag now only works on units.
Widget death event hooks are now executed before all actors are destroyed.
Added an option to the config to overwrite ALICE's table recycling functions with external ones.
Updated the Cameras CAT, which used the removed ALICE_SetRadius function.
Fixed a bug that caused the destroy methods of the hosts of additional actors anchored to an object to not be executed upon the parent object's death.
Fixed a typo that caused some tables to prevent garbage collection.
I updated the Cinematic Map to be compatible with the latest version and while doing that, fixed all the issues with the CAT libraries I encountered. There have been accumulating more bugs than I would have anticipated due to all the refactoring and changes to the main script, but hopefully I got most of them dealt with now.
Version 2.5.2
Updated the Cinematic Map to be compatible with the latest version.
Added the ALICE_FindIdentifier function, which returns the first entry in a list of identifiers for which an actor exists for a specified object.
selfInteractions is now a recognized flag that can be used as an alternative way to add self-interactions to an object.
Minor perfomance optimizations.
Fixed a typo that caused the ALICE_FindField function to not do anything (kekw).
Fixed a bug that caused the onCollision functions to sometimes crash when calculating the impact of a very fast object.
Fixed a bug that caused the CAT_AnimateShadow function to crash when the optional fields shadowX and shadowY were not set.
Fixed an issue that prevented the CAT_Knockback function form working on corpses.
Awarded Director's Cut due to how this resource expands beyond the concept of handling missiles: has an optimized algorithm for its own enumerations of units, destructibles, items, special effects and objects; has a great variety of API for one to add interactions between any type of object; has its own debug API; has a bunch of templates, called CATS, with lots of math involved which one can customize and add; has extensive documentation and configuration.
It's a breathtaking resource, full of devotion and great effort. - The Staff
I'm looking forward to seeing maps using this system.
Great work!
"WhAt'S tHiS sAfEtY cHeCk FoR? I cAn PrObAbLy ReMoVe It!"
—Past me, probably
Version 2.5.4
Fixed a bug that caused the enum functions to sometimes enumerate objects twice.
As GetUnitName and similar functions can return different results on different client languages when used on units with a default name, adding widget names to identifiers must now be enabled in the config, where there is a warning about possible desyncs.
Added the ALICE_DisableCallback function, which inherits the functionality of ALICE_DisablePeriodic, but can also interrupt callbacks queued up with ALICE_CallDelayed.
ALICE_DisablePeriodic is now deprecated.
Added the ALICE_PauseCallback function, which can pause or unpause a callback created by ALICE_CallDelayed, ALICE_CallPeriodic, or ALICE_CallRepeated.
Added the Walls CAT, which contains functions to detect and execute collisions of objects with vertical walls.
Added the Trees CAT, which adds the tree identifier to all trees.
Removed the UNRECOGNIZED_FIELD_WARNINGS config option and the ALICE_RecognizeFields function.
Removed the REQUIRED_FIELD_WARNINGS config option. An error message listing missing required fields in tables is now automatically generated on a thread crash. Missing required fields are now also listed in the actor tooltips.
With the ALICE_FuncRequireFields function, you can now define conditional required fields, i.e. fields that are only required if another field is also set.
You can now select an actor with ALICE_Select by identifier.
Fixed a bug in the Collisions 3D CAT with the special effect positioning on impact.
Added the isAnonymous actor flag. Anonymous actors cannot pair with other actors (only carry self-interactions), and cannot be enumerated, allowing all unnecessary overhead to be removed.
Removed the nil-argument warning in ALICE_DisableCallback, so that it is no longer necessary to check nil before disabling a callback.
Fixed a bug that caused the ALICE_DisableCallback function to crash the engine when used to disable the same callback multiple times.
Fixed a bug that caused the Objects CAT to crash the map on initialization when put above the ALICE script.
In an effort to make the CAT libraries easier to work with, I added annotations and did some refactoring. I hope the refactors will not cause any headaches. Updating the CATs is optional. The old versions work perfectly fine with the 2.7 main script.
Version 2.7
Added the ALICE_PairInterpolate function, which allows the interaction function to be called at a higher rate than the ALICE_Config.MIN_INTERVAL.
You can now define onDestroy functions for callbacks with ALICE_FuncSetOnDestroy. The onDestroy function will be executed after a callback using the specified function expires or when it is disabled.
Split the Objects CAT into the Interfaces, Units, and Data CATs.
All CAT config variables are now stored in the CAT_Data table.
Improved the included knockback system. Units being knocked against a wall will now continue to move into the direction in which they can still move. The unit's collision size is now taken into account. Items on the ground will no longer interrupt a knockback.
Added the CAT_GetUnitKnockbackSpeed and CAT_UnitEnableFriction functions.
All interaction functions in the CAT libraries are now annotated, listing all required and optional table fields for the respective function.
Fixed a bug that caused many API functions to not work in an onReset, onBreak, or onDestroy callback function of a self-interaction during actor destruction.
I'm quite new to all of this, so the code might make absolutely no sense at all, but this is my attempt at doing my best xD
I'm just trying to simply learn how to make a simple 2D Missile, and this is my approach but perhaps I'm going at it wrong? A000 is based off carrionswarm
Lua:
if Debug and Debug.beginFile then Debug.beginFile("Mage_Ability_Q") end
OnInit.final("Mage_Ability_Q", function()
function SlashHit(source, target)
print("onHit!")
-- print(GetUnitName(source), GetUnitName(target))
-- UnitDamageTarget(source, target, 25, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
end
Slash = {
x = nil,
y = nil,
vx = nil,
vy = nil,
visual = nil,
lifetime = nil,
--ALICE
identifier = "missile",
interactions = {unit = SlashHit},
--CAT
onUnitCollision = CAT_UnitCollisionCheck2D,
friction = 100,
elasticity = 0.7,
collisionRadius = 125
}
Slash.__index = Slash
function Slash.create(x, y, vx, vy)
local SlashMissile = {}
setmetatable(SlashMissile, Slash)
SlashMissile.x, SlashMissile.y = x, y
SlashMissile.vx, SlashMissile.vy = vx, vy
SlashMissile.visual = AddSpecialEffect("BladeBeamFinal.mdx", x, y)
ALICE_Create(SlashMissile)
return SlashMissile
end
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_CAST)
TriggerAddAction(trig, function ()
if GetSpellAbilityId() == FourCC('A000') then
print("Moving Slash")
CAT_Move2D(Slash.create(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), 500, 500))
end
end)
end)
if Debug and Debug.endFile then Debug.endFile() end
Your code looks solid, except for how you're using the CAT_Move2D function. CAT_Move2D is a self-interaction function, meaning it should be added to your object's selfInteractions table.
Self-interactions are functions that are executed with only the object itself as the argument. In this case, CAT_Move2D takes the missile and displaces it based on the vx and vy fields in the misile's table every step.
Your code looks solid, except for how you're using the CAT_Move2D function. CAT_Move2D is a self-interaction function, meaning it should be added to your object's selfInteractions table.
Self-interactions are functions that are executed with only the object itself as the argument. In this case, CAT_Move2D takes the missile and displaces it based on the vx and vy fields in the misile's table every step.
The only last question I would have is how to properly use the interactions table, like if my missile hit a unit, call the function SlashHit and be able to reference the hit unit (and the owner of the missile). Also how can I input direction for the missile? I assume vx and vy is the velocity for the x and y coordinates?
The collision check function is the interaction function between the two objects. Once they are within collision range, the onUnitCollision function set in your missile's table will be invoked, passing the missile and the unit as the arguments in that order.
vx and vy are the velocities of your missile and x and y the starting coordinates. If you want to set an owner for the missile, you have to set the owner field in your table when you create the missile. You can then retrieve it in your SlashHit function with missile.owner or ALICE_GetOwner(missile). Also, you can write any additional data you want into your table. Certain field names have special meaning for ALICE, certain field names have special meaning for the CAT functions, but you can set any other field and then reference it in your callbacks.
The collision check function is the interaction function between the two objects. Once they are within collision range, the onUnitCollision function set in your missile's table will be invoked, passing the missile and the unit as the arguments in that order.
vx and vy are the velocities of your missile and x and y the starting coordinates. If you want to set an owner for the missile, you have to set the owner field in your table when you create the missile. You can then retrieve it in your SlashHit function with missile.owner or ALICE_GetOwner(missile). Also, you can write any additional data you want into your table. Certain field names have special meaning for ALICE, certain field names have special meaning for the CAT functions, but you can set any other field and then reference it in your callbacks.
If you want to change the direction of the missile, you simply change the vx and vy fields. The CAT_Decay function, if put as a selfInteraction, will destroy your missile after lifetime seconds.
If you know the angle, then you can do the classic polar projection: vx = speed * math.cos(angleRadians)
vy = speed * math.cos(angleRadians)
where if you want to maintain the speed of the previous vx and vy then it's just pythagoras (speed^2 = vx^2 + vy^2)
Because I am as half nerd as Antares I implemented this on my own missiles CAT to get the speed from the components
Lua:
---@param unit unit
---@return number
local function GetUnitVelocity2DCombined(unit)
if unitVelocityX[unit] then --Units CAT
local totalSpeedSquared = unitVelocityX[unit]^2 + unitVelocityY[unit]^2
--If unit could not have traveled that fast naturally, it was most likely a teleport. Then, do not use velocity.
if totalSpeedSquared > UNIT_MAX_SPEED_SQUARED then
local totalSpeed = sqrt(totalSpeedSquared)
if knockbackSpeed[unit] then --Collisions CAT
local kbSpeed = sqrt(knockbackSpeed[unit].x^2 + knockbackSpeed[unit].y^2)
if totalSpeed > kbSpeed + UNIT_MAX_SPEED then
return kbSpeed
else
return totalSpeed
end
else
return 0
end
else
return sqrt(unitVelocityX[unit]*unitVelocityX[unit] + unitVelocityY[unit]*unitVelocityY[unit])
end
elseif knockbackSpeed[unit] then
return sqrt(knockbackSpeed[unit].x*knockbackSpeed[unit].x + knockbackSpeed[unit].y*knockbackSpeed[unit].y)
else
return 0
end
end
---Returns combined velocity of the components of the specified object.
---@param object unit | destructable | item | table
---@return number
function CAT_GetObjectVelocity2DCombined(object)
if type(object) == "table" then
if object.vx then
return math.sqrt(object.vx*object.vx + object.vy*object.vy)
elseif object.anchor then
if type(object.anchor) == "table" then
return math.sqrt(object.anchor.vx*object.anchor.vx + object.anchor.vy*object.anchor.vy)
elseif HandleType[object.anchor] == "unit" then
return GetUnitVelocity2DCombined(object.anchor)
else
return 0
end
else
return 0
end
else
if HandleType[object] == "unit" then
return GetUnitVelocity2DCombined(object)
else
return 0
end
end
end
Okay, spent some hours learning and this is my code which does what I want (what I envisioned) and without any errors. I do have 3 questions (made them as a comment in the code). Thanks @Wrda and @Antares
What is your thoughts, does it look bad, is there any improvement to be made anywhere? I did not find an accelerating function for a simple 2D missile, so I tried making my own. I'm mainly asking to make sure I'm using ALICE correctly, so that I'm not making things harder on myself
Lua:
if Debug and Debug.beginFile then Debug.beginFile("Mage_Ability_Q") end
OnInit.final("Mage_Ability_Q", function()
function SlashHit(gizmo, unit)
local missileOwner = GetOwningPlayer(gizmo.source)
if IsUnitEnemy(unit, missileOwner) and not gizmo.hitUnits[unit] then
UnitDamageTarget(gizmo.source, unit, 25, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
gizmo.hitUnits[unit] = true
end
end
Slash = {
x = nil,
y = nil,
vx = nil,
vy = nil,
visual = nil,
lifetime = 0.8,
source = nil,
--ALICE
identifier = "missile",
interactions = {unit = CAT_UnitCollisionCheck2D},
selfInteractions = {CAT_Move2D, CAT_Decay},
--CAT
onUnitCollision = SlashHit,
friction = 100,
elasticity = 0.7,
collisionRadius = 125,
missileSpeed = 800, -- Missile initial speed
maxSpeedMultiplier = 3 -- 300% faster than the initial speed
}
Slash.__index = Slash
function Slash:accelerate()
self.vx = self.vx * 1.05 -- Increase velocity by 5%
self.vy = self.vy * 1.05 -- Increase velocity by 5%
-- Limit the speed to 300% of the original speed, thanks Wrda for help on math
local maxSpeed = self.maxSpeedMultiplier * self.missileSpeed
local currentSpeed = math.sqrt(self.vx^2 + self.vy^2)
if currentSpeed > maxSpeed then
local reductionFactor = maxSpeed / currentSpeed
self.vx = self.vx * reductionFactor
self.vy = self.vy * reductionFactor
end
end
function Slash.create(x, y, vx, vy, source)
local SlashMissile = {}
setmetatable(SlashMissile, Slash)
SlashMissile.x, SlashMissile.y = x, y
SlashMissile.vx, SlashMissile.vy = vx, vy
SlashMissile.source = source
SlashMissile.visual = AddSpecialEffect("BladeBeamFinal.mdx", x, y)
SlashMissile.hitUnits = {}
CAT_InitDirectedOrientation(SlashMissile) -- Set proper orientation of the gizmo/missile (CAT_Orient2D did not work for me on selfInteractions)
ALICE_Create(SlashMissile)
-- Start the periodic acceleration
local periodicAccelerate = ALICE_CallPeriodic(function() SlashMissile:accelerate() end, 0.1)
-- Destroy the missile after its lifetime expires
ALICE_CallDelayed(function()
ALICE_Destroy(SlashMissile) -- Destroy the actor attached to the missile (is it needed? dont fully grasp actor yet) !!! [Question 1] !!!
DestroyEffect(SlashMissile.visual) -- Then destroy the associated visual effect, without this the effect still shows ingame
SlashMissile.visual = nil -- Is this part needed? !!! [Question 2] !!! I read Eikonium Lua guide, Lua Garbage Collector might make this not needed
ALICE_DisableCallback(periodicAccelerate) -- Stops the periodic timer (does it destroy, or is there memory leak? Not sure if Disable == destroy) !!! [Question 3] !!!
end, SlashMissile.lifetime) -- Example on metatables: I assume, since SlashMissile.lifetime does not exist, Lua checks Slash Table for this, due to the metatable (Slash.__index = Slash)
return SlashMissile
end
trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_CAST)
TriggerAddAction(trig, function ()
if GetSpellAbilityId() == FourCC('A000') then
local x1 = GetUnitX(GetTriggerUnit())
local y1 = GetUnitY(GetTriggerUnit())
local x2 = GetSpellTargetX()
local y2 = GetSpellTargetY()
local angle = GetAngle(y1, y2, x1, x2)
Slash.create(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), Slash.missileSpeed * math.cos(angle), Slash.missileSpeed * math.sin(angle), GetTriggerUnit())
end
end)
end)
if Debug and Debug.endFile then Debug.endFile() end
The code below is referenced in the code above, but is in another file I made (not sure if ALICE has utilities that does stuff like this for you)
Lua:
---Get Angle from x1/y1 to x2/y2
---@param y1 number (From Where)
---@param y2 number (To Where)
---@param x1 number (From Where)
---@param x2 number (To Where)
---@return number
function GetAngle(y1, y2, x1, x2)
return Atan2(y2 - y1, x2 - x1)
end
function Slash.create(x, y, vx, vy, source)
local SlashMissile = {}
setmetatable(SlashMissile, Slash)
SlashMissile.x, SlashMissile.y = x, y
SlashMissile.vx, SlashMissile.vy = vx, vy
SlashMissile.source = source
SlashMissile.visual = AddSpecialEffect("BladeBeamFinal.mdx", x, y)
SlashMissile.hitUnits = {}
CAT_InitDirectedOrientation(SlashMissile) -- Set proper orientation of the gizmo/missile (CAT_Orient2D did not work for me on selfInteractions)
ALICE_Create(SlashMissile)
-- Start the periodic acceleration
local periodicAccelerate = ALICE_CallPeriodic(function() SlashMissile:accelerate() end, 0.1)
-- Destroy the missile after its lifetime expires
ALICE_CallDelayed(function()
ALICE_Destroy(SlashMissile) -- Destroy the actor attached to the missile (is it needed? dont fully grasp actor yet) !!! [Question 1] !!!
DestroyEffect(SlashMissile.visual) -- Then destroy the associated visual effect, without this the effect still shows ingame
SlashMissile.visual = nil -- Is this part needed? !!! [Question 2] !!! I read Eikonium Lua guide, Lua Garbage Collector might make this not needed
ALICE_DisableCallback(periodicAccelerate) -- Stops the periodic timer (does it destroy, or is there memory leak? Not sure if Disable == destroy) !!! [Question 3] !!!
end, SlashMissile.lifetime) -- Example on metatables: I assume, since SlashMissile.lifetime does not exist, Lua checks Slash Table for this, due to the metatable (Slash.__index = Slash)
return SlashMissile
end
I don't understand why you're using the callback API for some of your functions when you're already using the self-interaction table successfully for other functions. There is a lot of overlap between those two features and the callback API is more flexible in many ways, but self-interactions are more convenient to use for instantiated objects and have better performance.
Lua:
-- Set proper orientation of the gizmo/missile (CAT_Orient2D did not work for me on selfInteractions)
That is weird. I can't tell you why. You're using vx and vy, so it should be correct.
From what I can tell, you want your missile to pass through the unit and damage it once. There is an onCollision function included in the collisions CAT that does just that: CAT_UnitPassThrough2D. If you use this function, you can use your SlashHit function as the onUnitCallback function. But, if you don't want any effect on hit other than 25 damage, you can just set onUnitDamage = 25.
If you want to do it manually without CAT_UnitPassThrough2D, calling ALICE_PairDisable in the SlashHit function is easier than storing a table of all units that were hit.
You can add the SlashMissile.accelerate function as a self-interaction. You just have to make sure to define it above the self-interactions table definition, otherwise it will be nil by the time you assign it. This might also be the cause of some of the other problems you encountered.
Lua:
-- Destroy the actor attached to the missile (is it needed? dont fully grasp actor yet) !!! [Question 1] !!!
No, the CAT_Decay function will destroy the entire object by invoking ALICE_Kill. In general, ALICE_Kill is the more useful function while ALICE_Destroy has more niche use cases. ALICE_Destroy essentially unregisters the object with ALICE by destroying a single actor. ALICE_Kill tries to infer what the correct destructor for the object itself is, invokes it, then destroys all actors attached to that object.
Lua:
-- Then destroy the associated visual effect, without this the effect still shows ingame
Again, it looks like the CAT_Decay function was actually not added to your object. Enable the debug mode in-game, pause the game with Ctrl + T and see what the tooltip says about self-interactions.
Lua:
---Get Angle from x1/y1 to x2/y2
---@param y1 number (From Where)
---@param y2 number (To Where)
---@param x1 number (From Where)
---@param x2 number (To Where)
---@return number
function GetAngle(y1, y2, x1, x2)
return Atan2(y2 - y1, x2 - x1)
end
I don't understand why you're using the callback API for some of your functions when you're already using the self-interaction table successfully for other functions. There is a lot of overlap between those two features and the callback API is more flexible in many ways, but self-interactions are more convenient to use for instantiated objects and have better performance.
Lua:
-- Set proper orientation of the gizmo/missile (CAT_Orient2D did not work for me on selfInteractions)
That is weird. I can't tell you why. You're using vx and vy, so it should be correct.
From what I can tell, you want your missile to pass through the unit and damage it once. There is an onCollision function included in the collisions CAT that does just that: CAT_UnitPassThrough2D. If you use this function, you can use your SlashHit function as the onUnitCallback function. But, if you don't want any effect on hit other than 25 damage, you can just set onUnitDamage = 25.
If you want to do it manually without CAT_UnitPassThrough2D, calling ALICE_PairDisable in the SlashHit function is easier than storing a table of all units that were hit.
You can add the SlashMissile.accelerate function as a self-interaction. You just have to make sure to define it above the self-interactions table definition, otherwise it will be nil by the time you assign it. This might also be the cause of some of the other problems you encountered.
Lua:
-- Destroy the actor attached to the missile (is it needed? dont fully grasp actor yet) !!! [Question 1] !!!
No, the CAT_Decay function will destroy the entire object by invoking ALICE_Kill. In general, ALICE_Kill is the more useful function while ALICE_Destroy has more niche use cases. ALICE_Destroy essentially unregisters the object with ALICE by destroying a single actor. ALICE_Kill tries to infer what the correct destructor for the object itself is, invokes it, then destroys all actors attached to that object.
Lua:
-- Then destroy the associated visual effect, without this the effect still shows ingame
Again, it looks like the CAT_Decay function was actually not added to your object. Enable the debug mode in-game, pause the game with Ctrl + T and see what the tooltip says about self-interactions.
Always use the Lua math functions instead of natives, math.atan in this case. They're ~5x faster.
Spent the night reading through multiple files of documentation and used your comment as guidance, and this is my improvised code, does it pass in your eyes? Atan2 was changed to math.atan, and I know about the simple onDamage, but I prefer this, since I will be able to do more, like creating bloodsplash effect on units hit etc. But for very simple things like only needing to deal damage, it is quite convenient to have the option to simply use onDamage.
Lua:
if Debug and Debug.beginFile then Debug.beginFile("Mage_Ability_Q") end
OnInit.final("Mage_Ability_Q", function()
function SlashHit(gizmo, unit)
local missileOwner = GetOwningPlayer(gizmo.source)
if IsUnitEnemy(unit, missileOwner) then
UnitDamageTarget(gizmo.source, unit, 25, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
end
end
function SlashAccelerate(slash)
slash.vx = slash.vx * 1.05 -- Increase velocity by 5%
slash.vy = slash.vy * 1.05 -- Increase velocity by 5%
-- Limit the speed to 300% of the original speed, thanks Wrda for help on math
local maxSpeed = slash.maxSpeedMultiplier * slash.missileSpeed
local currentSpeed = math.sqrt(slash.vx^2 + slash.vy^2)
if currentSpeed > maxSpeed then
local reductionFactor = maxSpeed / currentSpeed
slash.vx = slash.vx * reductionFactor
slash.vy = slash.vy * reductionFactor
end
end
Slash = {
x = nil,
y = nil,
vx = nil,
vy = nil,
visual = nil,
lifetime = 0.8,
source = nil,
missileSpeed = 800, -- Missile initial speed
maxSpeedMultiplier = 3, -- 300% faster than the initial speed
--ALICE
identifier = "missile",
interactions = {unit = CAT_UnitCollisionCheck2D},
selfInteractions = {CAT_Move2D, CAT_Decay, CAT_Orient2D, SlashAccelerate},
--CAT
onUnitCollision = CAT_UnitPassThrough2D,
onUnitCallback = SlashHit,
friction = 100,
elasticity = 0.7,
collisionRadius = 125
}
Slash.__index = Slash
function Slash.create(x, y, vx, vy, source)
local SlashMissile = {}
setmetatable(SlashMissile, Slash)
SlashMissile.x, SlashMissile.y = x, y
SlashMissile.vx, SlashMissile.vy = vx, vy
SlashMissile.source = source
SlashMissile.visual = AddSpecialEffect("BladeBeamFinal.mdx", x, y)
ALICE_Create(SlashMissile)
return SlashMissile
end
local trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_CAST)
TriggerAddAction(trig, function ()
if GetSpellAbilityId() == FourCC('A000') then
local x1 = GetUnitX(GetTriggerUnit())
local y1 = GetUnitY(GetTriggerUnit())
local x2 = GetSpellTargetX()
local y2 = GetSpellTargetY()
local angle = GetAngle(y1, y2, x1, x2)
Slash.create(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), Slash.missileSpeed * math.cos(angle), Slash.missileSpeed * math.sin(angle), GetTriggerUnit())
end
end)
end)
if Debug and Debug.endFile then Debug.endFile() end
Your friction and elasticity fields are currently unused.
The owner check can be done by setting the friendlyFire field to false. If you do it that way, you need to set the owner field for your missile on creation, then you can remove the owner check in your SlashHit function. This will save the interpreter from doing all those calculations in the CAT_UnitPassThrough2D function just to be thrown out at the very end. It will also lead to a different behavior should you choose to use a different collision function at some point. If you use CAT_UnitImpact2D, using friendlyFire = false will make the missile pass through allied units; using the owner check the way you do, it will make them impact on allied units, just not deal any damage.
If you want to create special effects like blood, make sure to use the collision point that the collision functions calculate for you. They're passed as the 3rd to 5th argument into your callback.
Your friction and elasticity fields are currently unused.
The owner check can be done by setting the friendlyFire field to false. If you do it that way, you need to set the owner field for your missile on creation, then you can remove the owner check in your SlashHit function. This will save the interpreter from doing all those calculations in the CAT_UnitPassThrough2D function just to be thrown out at the very end. It will also lead to a different behavior should you choose to use a different collision function at some point. If you use CAT_UnitImpact2D, using friendlyFire = false will make the missile pass through allied units; using the owner check the way you do, it will make them impact on allied units, just not deal any damage.
If you want to create special effects like blood, make sure to use the collision point that the collision functions calculate for you. They're passed as the 3rd to 5th argument into your callback.
The ALICE_AddSelfInteraction function can now accept a data table that can be loaded with ALICE_PairLoadData.
The ALICE_SetInteractionFunc function now affects already established pairs.
Added minor optimizations.
Fixed a bug that could cause the ALICE_DisableCallback function to disable a different callback if the original one had already been disabled and recycled.
Fixed a bug that caused the ALICE_PairInterpolate function to sometimes crash the cycle when used in conjunction with the callback API.
Fixed a bug that caused a crash when setting an object that has global actors attached to it to stationary.
Fixed a bug that caused the zOffset flag to be ignored for table objects with no z-coordinates.
Fixed an issue where references to actors on objects would not immediately get removed when the actor was destroyed, leading to potential unexpected behavior.
Fixed a bug that caused a crash when attempting to return the cellCheckInterval flag of a stationary object.
The ALICE error handling system is working again and does now print appropriate error messages for crashes caused by callback functions.
New global: ALICE_Where. Denotes where in the ALICE cycle we currently are.
Added the MultiPassThrough onCollision callback functions, which trigger every time the gizmo passes through an object.
Added the CAT_MoveSound function to the Effects CAT, which attaches a 3D sound to an object.
Fixed a bug that caused ALICE_OnCreationAddIdentifier to overwrite other identifier hooks that were created for the other identifiers of an object that has multiple.
Fixed an issue where onUnitEnter hooks would fire on units created by CreateUnit and similar functions, even if they did not receive a unit actor.
Fixed a bug that could rarely cause multiple API functions to crash the system by causing a double-deletion of pairs.
I restructured and improved the tutorial. It now begins with the callback API to introduce the return value syntax, continues with enumerators to introduce actors and identifiers, and only then goes into spatial hashing and interaction functions instead of throwing them at you right away.
Version 2.8.3
Selecting an actor in debug mode now requires Ctrl to be pressed.
Add the ALICE_NextStep function to the debug library.
Added the actorClass flag. Setting an actor class allows ALICE to recycle actors and save time initializing various fields, shaving off a few microseconds during actor creation. This is useful for objects with an extremely high creation frequency.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.