- Joined
- Sep 29, 2014
- Messages
- 49
I wasn't quite sure where to post this specifically, so hopefully this is the right thread.
While this is something to be used with triggering, there is not much specific and in-depth coding. It is also compatible with both GUI and scripting, so posting it on only one of the threads does not feel right. It is also not a Spell, acts as a support for other Systems rather than being a System on its own and not exactly a template since its usage really depends on what you want, so I also feel it does not belong on there.
So mods, should you find this placement disagreeable, feel free to move it around.
While this is something to be used with triggering, there is not much specific and in-depth coding. It is also compatible with both GUI and scripting, so posting it on only one of the threads does not feel right. It is also not a Spell, acts as a support for other Systems rather than being a System on its own and not exactly a template since its usage really depends on what you want, so I also feel it does not belong on there.
So mods, should you find this placement disagreeable, feel free to move it around.
Tips & Tricks
Using Abilities as Data Reference for Triggering
For the remainder of this thread, I'm going to call this method PRAT (Procedural Referencing with Ability object for Triggering) to make it easier.I recently came up with this method and could not find any mention of something similar in Hiveworkshop or cases where its being used in projects. Although of course, the applications of this are quite limited outside of systems and multi-map projects like custom campaign, so I guess not many consider something like this important.
ASCII table scripts are not always required but are essential to unlock the full potential of PRAT, you can find some here.
What's the Point of Implementing This?
The aim of PRAT is to reduce the number of times you need to dive into your Triggers (whether it be a script like vJass and Lua or GUI) to make adjustments or fine tuning. Instead of defining constants inside of the Trigger, we can instead call the Data Field of a custom ability. By doing this, the trigger will be more scalable to larger projects like Custom Campaigns or System/Templates.How Does This Work?
First things first, the functions we will be using are not theAbility - Get Integer/Real/String/Boolean Field - XXX
types. These are inefficient for our workflow. To even be able to get this method, we have to go through:-
Set TmpPointA = (Center of (Playable map area))
-
Unit - Create 1 DummyUnit for Neutral Passive at TmpPointA facing Default building facing degrees
-
Custom script: call RemoveLocation(udg_TmpPointA)
-
Unit - Add PRAT to (Last created unit)
-
Set var = Ability - Get Integer/Real/String/Boolean Field - XXX
-
Unit - Remove (Last created unit) from the game
GUI
Jass
-
Set var = (Mana cost of PRAT , Level X)
-
Note: Integer above is available in dropdown menu as "Ability - Ability Mana Cost"
-
Set var = (Cooldown of PRAT , Level X)
-
Note: Real above is available in dropdown menu as "Game - Ability Cooldown"
-
Set var = (Tooltip of PRAT for level X)
-
Note: String above is available in dropdown menu as "Ability - Ability Tooltip"
-
-------- And more of their kinds --------
JASS:
set var = BlzGetAbilityTooltip('prat', X)
set var = BlzGetAbilityCooldown('prat', X)
set var = BlzGetAbilityTooltip('prat', X)
//-------- And more of their kinds --------
Why Abilities?
Some of you might notice that units have similar function calls available, but here are why abilities are superior in option.Units have way too many Editor fields that are are not of our interest, we cannot get the data from
Combat - Attack 1 - Cooldown Time
, Text - Tooltip - Extended
or Movement - Speed Base
and these fields are cluttering our view.Abilities on the other hand have minimal "Unused Fields" and we can even control the numbers with
Stats - Levels
. On top of that, the ones we want to use are grouped relatively close near the bottom.Finally, check this table below. This is the approximate number of data you can get from each type.
Directly Accessible Data
|
Ability
|
Unit-Type
|
Item-Type
|
Integer
|
1 per Level (Max 100)
|
3
|
0
|
Real
|
1 per Level (Max 100)
|
0
|
0
|
String
|
4 per Level (Max 400) + 2 (Not Including Name and Art Paths) |
1
|
0
|
Boolean
|
3
|
8
|
3
|
Keep in mind that Strings are really powerful once you know some string manipulations. For example, you can list multiple things in one String Field and separate them with "," (comma) or " " (space). And then in the trigger, you can split where to assign using substring and other methods.
Update:
Found out from here that it is possible to store several strings inside a unit. I still would not recommend it over abilities, but here is the details after several testings:
- You can store strings, mostly inside
Art - Special
andArt - Target
- Yes, you can store any string inside. It does not have to be a model
- For information:
Art - Special
determines what model appears when unit dies from Artillery (Other sources of damage might also do it, but as far as I know, Artillery is the easiest example).Art - Target
YES IT DOES DO SOMETHING determines what model appears to secondary targets hit by Weapon Type:Missile (Line)
andArtillery (Line)
. It does not appear on the main target. It does not appear on other Weapon Types with multi target attacks.
- When more than one model/line, it bugs out entirely. Not showing anything on the events I mentioned above.
- Those two fields presented themselves as array, but apparently it is not. Atleast when called using
GetAbilityEffectById
- Using
GetAbilityEffectById(GetUnitTypeId(unitInstance), EFFECT_TYPE_TARGET, X)
with X as any integer value. It returns all of its content separated by a "," regardless of X's value. Still useful but will require a bit more work.
- Using
- It is less intuitive
- String Fields in ability are separated into four sections more or less with the tooltip types, which you can treat as a category or separator.
- There is also the Learn Tooltip which I myself frequently use as a note/comment to guide or keep track of what is what.
- You cannot have all of this in Unit.
- It also still requires an instance of a unit in the map for it to work.
- However,
- It is of course very useful if the data is a unit-type specific, since you won't need to create some convoluted links between multiple FourCC codes in a PRAT. By extension, reducing the amount of PRAT required.
- For some concerns regarding fields being necessary:
- With a little tinkering using Bribe's Damage Engine and Attack Indexer, it should be possible to replicate the intended effect.
- You can store the Actual Data to the first index, only treat only indexes after it as the data field.
- By detecting weapon type artillery on a death event, you can spawn a special effect based on the first data.
- By detecting weapon type XX (Line), you can spawn a special effect on collateral targets. (Using Attack Indexer)
Examples:
The implementation of PRAT really varies on what you want, so instead of explaining and telling the steps, I will just show you some examples of how I used it so far.Spell Events of Spells with Similar Nature
Pseudo Linked List of Spells
As a String Database 1
As a String Database 2
Custom Item Drop Tables
This was the first instance I ever utilized PRAT in full.
Goal:
Expand on Bribe's HealEvent to detect Healing Sources using Magtheridon96's GUI SpellEvent.Issue:
A functional Heal Detection System need to be able to register any healing abilities being cast. While it is possible to register all default healing abilities to SpellEvent, any new abilities require modification to the triggers. One or two new abilities might not be too bad, but multiple more and put the system into a custom campaign that is constantly being updated... imagine the amount of work.PRAT's Solution:
So, let's create a PRAT, under the code 'Dhsi'.I will be using "Text - Tooltip - Normal" to store the FourCC/AbilityID codes of each ability. To count the number of abilities in the list, I will be putting it in "Level 1 - Stats - Mana Cost". The resulting PRAT should look similar to below.
As a bonus, I can also give a description by turning it to a Hero Ability and put some notes in the "Text - Tooltip - Learn - Extended". This should help other editors to understand what to modify should they want to.
-
set SpellEventAbility[0] = Heal (Priest)
-
set SpellEventAbility[1] = Heal (Neutral Hostile 1)
-
set SpellEventAbility[2] = Heal (Neutral Hostile 2)
-
set SpellEventAbility[3] = Heal (Neutral Hostile 3)
-
set SpellEventAbility[4] = Holy Light
-
set SpellEventAbility[5] = Holy Light (Item)
-
set SpellEventAbility[6] = Death Coil
-
For each (Integer A) from 0 to 6 do (Actions)
-
Loop - Actions
-
Set SpellEventTrigger[(Integer A)] = STHealEvents <gen>
-
-
-
set SpellEventAbility[7] = Essence of Blight
-
set SpellEventAbility[8] = Item Area Healing (Greater)
-
For each (Integer A) from 7 to 8 do (Actions)
-
Loop - Actions
-
Set SpellEventTrigger[(Integer A)] = AOEHealEvents <gen>
-
-
-
Trigger - Run GUISpellEvent <gen> (Ignoring conditions)
-
Set SpellEventMultLoopIndex = 0
-
For each (Integer A) from 0 to ((Mana cost of PRAT - HealEvent ST Instant , Level 0) - 1) do (Actions)
-
Loop - Actions
-
Custom script: set udg_SpellEventAbility[udg_SpellEventMultLoopIndex] = S2A(BlzGetAbilityTooltip('Dhsi', GetForLoopIndexA()))
-
Set SpellEventTrigger[SpellEventMultLoopIndex] = STHealEvents <gen>
-
Set SpellEventMultLoopIndex = (SpellEventMultLoopIndex + 1)
-
-
-
For each (Integer A) from 0 to ((Mana cost of PRAT - HealEvent AOE Instant , Level 0) - 1) do (Actions)
-
Loop - Actions
-
Custom script: set udg_SpellEventAbility[udg_SpellEventMultLoopIndex] = S2A(BlzGetAbilityTooltip('Dhai', GetForLoopIndexA()))
-
Set SpellEventTrigger[SpellEventMultLoopIndex] = AOEHealEvents <gen>
-
Set SpellEventMultLoopIndex = (SpellEventMultLoopIndex + 1)
-
-
-
Trigger - Run GUISpellEvent <gen> (Ignoring conditions)
1. Add new
set SpellEventAbility[X] = NewAbility
2. Modify the loop number manually for
Set SpellEventTrigger[(Integer A)] = AOEHealEvents <gen>
I can instead:
1. Add 1 to PRAT level and manacost
2. Add the FourCC code into the tooltip of new level
When listed like this, the difference might not be that big. However,
- Changing fields in ObjectEditor is faster than changing lines in trigger, especially for GUI.
- When using in custom campaign (which I am doing), the trigger modification number is multiplied with the number of maps I have.
Footnote
While PRAT did its job and was able to detect newly added healing abilities, it was unfortunate that both events Begin Casting and Starts the Effect fires after the healing was done instead of just before
Goal:
Detect when workers start mining a gold mine.Issue:
Every "Gold Mine" type requires a different "Harvest" abilities, trigger should not run when a Human Peasant right-clicks a Haunted Gold Mine.There are cases where maps have custom Gold Mine like SC2 Geyser replication for example. The system need to be able to detect it without extensive modification.
PRAT's Solution:
So, let's create a PRAT, under name "PRAT - Mine Harvest Ability Link".I will be using:
- "Text - Tooltip - Normal" to store the FourCC/AbilityID codes of the Harvest Ability.
- "Text - Tooltip - Normal - Extended" to store the FourCC/AbilityID codes of the Gold Mine Ability.
- "Text - Tooltip - Turn Off" to store the integer which will refer to what the Gold Mine ability is based on. (e.g. Custom Gold Mines based on Blighted Gold Mine are assigned 2)
- "Text - Tooltip - Turn Off - Extended" refers to the Code IDs of "Text - Tooltip - Turn Off"
My triggers changes from:
-
Set GMC_GOLD_MINE_NORMAL = 0
-
Set GMC_GOLD_MINE_BLIGHTED = 1
-
Set GMC_GOLD_MINE_ENTANGLED = 2
-
-------- Ability Codes --------
-
Set GMC_MineCodeVariants = 3
-
Set GMC_CodeMineKeyAbility[0] = Gold Mine ability
-
Set GMC_CodeWorkKeyAbility[0] = Harvest (Gold and Lumber)
-
Set GMC_MineType[0] = GMC_GOLD_MINE_NORMAL
-
Set GMC_CodeMineKeyAbility[1] = Gold Mine ability
-
Set GMC_CodeWorkKeyAbility[1] = Harvest (Neutral)
-
Set GMC_MineType[1] = GMC_GOLD_MINE_NORMAL
-
Set GMC_CodeMineKeyAbility[2] = Blighted Gold Mine ability
-
Set GMC_CodeWorkKeyAbility[2] = Gather(Acolyte Gold)
-
Set GMC_MineType[2] = GMC_GOLD_MINE_BLIGHTED
-
Set GMC_CodeMineKeyAbility[3] = Load (Entangled Gold Mine)
-
Set GMC_CodeWorkKeyAbility[3] = Gather (Wisp Gold and Lumber)
-
Set GMC_MineType[3] = GMC_GOLD_MINE_ENTANGLED
-
Set GMC_PRAT = PRAT - Mine Harvest Ability Link
-
Custom script: set udg_GMC_GOLD_MINE_NORMAL = S2I(BlzGetAbilityActivatedExtendedTooltip(udg_GMC_PRAT, 0))
-
Custom script: set udg_GMC_GOLD_MINE_BLIGHTED = S2I(BlzGetAbilityActivatedExtendedTooltip(udg_GMC_PRAT, 1))
-
Custom script: set udg_GMC_GOLD_MINE_ENTANGLED = S2I(BlzGetAbilityActivatedExtendedTooltip(udg_GMC_PRAT, 2))
-
-------- Ability Codes --------
-
Set GMC_MineCodeVariants = (Mana cost of GMC_PRAT, Level 0)
-
For each (Integer A) from 0 to (GMC_MineCodeVariants - 1), do (Actions)
-
Loop - Actions
-
Custom script: set udg_GMC_CodeWorkKeyAbility[bj_forLoopAIndex] = S2A(BlzGetAbilityTooltip(udg_GMC_PRAT, bj_forLoopAIndex))
-
Custom script: set udg_GMC_CodeMineKeyAbility[bj_forLoopAIndex] = S2A(BlzGetAbilityExtendedTooltip(udg_GMC_PRAT, bj_forLoopAIndex))
-
Custom script: set udg_GMC_MineType[bj_forLoopAIndex] = S2I(BlzGetAbilityActivatedTooltip(udg_GMC_PRAT, bj_forLoopAIndex))
-
-
Goal:
Modify UI to show units level and classification(s). Should also allow modifications of the classification names.Issue:
As far as I know, getting data from Game Interface and Game Constants are not possible with triggers.Also 'Ancient' and 'Town Hall' are two classifications with no string attached to it.
PRAT's Solution:
So, let's create a PRAT, under the code 'Dfrt'.I will be using "Text - Tooltip - Normal" to store the string data of the classifications. I will also utilize the "Stats - Mana Cost" as a Boolean here, 0 will make it that the trigger ignore the classification, otherwise use as normal.
As a bonus, I can also give a description by turning it to a Hero Ability and put some notes in the "Text - Tooltip - Learn - Extended". This should help other editors to understand what to modify should they want to.
JASS:
library moCustomUI initializer Init
globals
//Controls
private real updateFreq = 0.03
//FrameHandles
private framehandle fhClassValue
//AbilityCodeLists
private integer acClassText
//Classifications
private string textGiant
private string textMechanical
private string textTauren
private string textUndead
private string textTownHall
private string textAncient
private integer showGiant
private integer showMechanical
private integer showTauren
private integer showUndead
private integer showTownHall
private integer showAncient
//Others
private group currGroup
private unit currUnit
private unit prevUnit
endglobals
public function UpdateFrames takes nothing returns nothing
local string tmpText = ""
local real tmpReal = 0.0
local boolean isNotBuild = false
call GroupEnumUnitsSelected(currGroup, GetLocalPlayer(), null)
set currUnit = FirstOfGroup(currGroup)
call GroupClear(currGroup)
if currUnit != prevUnit then
//---------------
//Classifications
//---------------
set tmpText = ""
set isNotBuild = not IsUnitType(currUnit, UNIT_TYPE_STRUCTURE)
//Ignore Level if Stucture
if IsUnitType(currUnit, UNIT_TYPE_STRUCTURE) then
set tmpText = tmpText + " "
else
set tmpText = tmpText + " " + GetLocalizedString("LEVEL")
//Check Hero
if IsUnitType(currUnit, UNIT_TYPE_HERO) then
set tmpText = tmpText + " " + I2S(GetHeroLevel(currUnit))
else
set tmpText = tmpText + " " + I2S(GetUnitLevel(currUnit))
endif
endif
//Classifications
if IsUnitType(currUnit, UNIT_TYPE_MECHANICAL) and showMechanical != 0 and isNotBuild then
set tmpText = tmpText + " " + textMechanical
endif
if IsUnitType(currUnit, UNIT_TYPE_GIANT) and showGiant != 0 and isNotBuild then
set tmpText = tmpText + " " + textGiant
endif
if IsUnitType(currUnit, UNIT_TYPE_ANCIENT) and showAncient != 0 then
set tmpText = tmpText + " " + textAncient
endif
if IsUnitType(currUnit, UNIT_TYPE_TOWNHALL) and showTownHall != 0 then
set tmpText = tmpText + " " + textTownHall
endif
if IsUnitType(currUnit, UNIT_TYPE_UNDEAD) and showUndead != 0 and isNotBuild then
set tmpText = tmpText + " " + textUndead
endif
if IsUnitType(currUnit, UNIT_TYPE_TAUREN) and showTauren != 0 and isNotBuild then
set tmpText = tmpText + " " + textTauren
endif
//Hero Name
if IsUnitType(currUnit, UNIT_TYPE_HERO) then
set tmpText = tmpText + " " + GetUnitName(currUnit)
endif
set tmpText = SubStringBJ(tmpText, 2, StringLength(tmpText))
call BlzFrameSetText(BlzGetFrameByName("moSimpleUnitStatsPanel", 0), tmpText)
set prevUnit = currUnit
set currUnit = null
endif
endfunction
public function InitFrames takes nothing returns nothing
local timer tmpTimer = CreateTimer()
call BlzLoadTOCFile("GameEssentials\\moCustomUIData.toc")
//FrameHandles
set fhClassValue = BlzGetFrameByName("moSimpleUnitStatsPanel", 0)
//AbilityCodeLists
set acClassText = 'Dfrt'
//Classifications
set textGiant = BlzGetAbilityTooltip(acClassText, 0)
set textMechanical = BlzGetAbilityTooltip(acClassText, 1)
set textTauren = BlzGetAbilityTooltip(acClassText, 2)
set textUndead = BlzGetAbilityTooltip(acClassText, 3)
set textTownHall = BlzGetAbilityTooltip(acClassText, 4)
set textAncient = BlzGetAbilityTooltip(acClassText, 5)
set showGiant = BlzGetAbilityManaCost(acClassText, 0)
set showMechanical = BlzGetAbilityManaCost(acClassText, 1)
set showTauren = BlzGetAbilityManaCost(acClassText, 2)
set showUndead = BlzGetAbilityManaCost(acClassText, 3)
set showTownHall = BlzGetAbilityManaCost(acClassText, 4)
set showAncient = BlzGetAbilityManaCost(acClassText, 5)
//Others
set prevUnit = null
//Setup
call BlzFrameSetVisible(BlzGetFrameByName("SimpleUnitStatsPanel", 0), false)
call BlzFrameSetSize(BlzGetFrameByName("SimpleUnitStatsPanel", 0), 0.00001, 0.00001)
call BlzCreateSimpleFrame("moSimpleUnitStatsPanel", BlzGetFrameByName("SimpleInfoPanelUnitDetail", 0), 0)
call TimerStart(tmpTimer, updateFreq, true, function UpdateFrames)
endfunction
public function Init takes nothing returns nothing
local timer tmpTimer = CreateTimer()
set currGroup = CreateGroup()
call TimerStart(tmpTimer, 0.0, false, function InitFrames)
call DestroyTimer(tmpTimer)
endfunction
endlibrary
These are the first instances of me using PRAT for minor usages.
Goal:
Provide easier way to change Cinematic Transmission Names.It is not rare for us to play a campaign, control a hero with name like "Brian" for example. But during cinematics, instead of simply "Gardon" it becomes "Lord Brian the Fallen" when speaking. Most creators will simply write this string data down in the
Transmission
trigger, or the meticulous ones will store it in a variable first. Regardless, when creating campaigns with multiple maps, in the case where you want to change this name to for example "Baby Brian the Droller", having to revisit each map and change it will become cumbersome.PRAT's Solution:
For this example, I'm using a Scourge of Lordaeron, and I made two PRAT, one to store Arthas' prefix and suffix, and another for Uther's. Prefixes are stored in "Level 0 Text - Tooltip - Normal" and Suffixes in "Level 1 Text - Tooltip - Normal". With such setup, now the triggering looks more like this, and if I want to change Uther's title from " the Lightbringer" to " the Death Bringer", I can do so from the object editor and it will apply everywhere.Goal:
Trigger a random item drop based on drop table.Issue:
Item - Create
trigger only have the option of Item-Type Of Item
, Random Item-Type
, Random Item-Type Of Class
. With the latter 2 only takes level and cannot read the Item-Type's "Stats - Include As Random Choice" normally used by melee map makers.How to incorporate these drops without moving every single item to specific classification which might cause it to be unintuitive for item editing.
There might be a need to create several different drop tables for different units.
PRAT's Solution:
For this Drop Table, I want different table for different Levels.I created 6 different PRAT, each for level 1 to 6, and an additional PRAT (MasterTable) to call the other PRATs in the Triggers.
I will be using "Text - Tooltip - Normal" to store the FourCC code of items I want inside the drop table.
When applied to Triggers:
-
Hashtable - Create a hashtable
-
Set DR_ItemTypeHash = (Last created hashtable)
-
Set DR_ItemDropTableLevels = (Mana cost of PRAT - Drop Table (Permanent - MasterTable) , Level 0)
-
For each (Integer A) from 0 to (DR_ItemDropTableLevels - 1), do (Actions)
-
Loop - Actions
-
Custom script: call BJDebugMsg( "Setting up Table for Level " + I2S(bj_forLoopAIndex + 1) )
-
Custom script: set udg_DR_ItemDropTableCode[bj_forLoopAIndex] = S2A(BlzGetAbilityTooltip('DdpM', GetForLoopIndexA()))
-
For each (Integer B) from 0 to ((Mana cost of DR_ItemDropTableCode[(Integer A)], Level 0) - 1), do (Actions)
-
Loop - Actions
-
Custom script: set udg_TmpInt = S2A(BlzGetAbilityTooltip(udg_DR_ItemDropTableCode[bj_forLoopAIndex], GetForLoopIndexB()))
-
Hashtable - Save TmpInt as (Integer A) of (Integer B) in DR_ItemTypeHash
-
-
-
-
-
Set DR_RandomNumber = (Random real number between 0.00 and 1.00)
-
For each (Integer A) from 1 to 6, do (Actions)
-
Loop - Actions
-
Set TmpReal = (Load (Min(8, (DR_UnitLvForDrop))) of (Integer A) from DR_Chance_ILv_Perm)
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DR_RandomNumber Less than TmpReal
-
-
Then - Actions
-
Set TmpInt = (Random integer number between 0 and (Mana cost of DR_ItemDropTableCode[((Integer A) - 1)], Level 0))
-
Custom script: set udg_DR_TmpItemCode = LoadIntegerBJ(bj_forLoopAIndex - 1, udg_TmpInt, udg_DR_ItemTypeHash)
-
Item - Create DR_TmpItemCode at DR_UnitDropPosition
-
Custom script: call RemoveLocation(udg_DR_UnitDropPosition)
-
Skip remaining actions
-
-
Else - Actions
-
Set DR_RandomNumber = (DR_RandomNumber - TmpReal)
-
-
-
-
DR_Chance_ILv_Perm
hashtable.Attachments
Last edited: