|
| 1 | +--- |
| 2 | +title: Generic Barrier Base Class |
| 3 | +author: Cykada |
| 4 | +steamId: '76561198066443130' |
| 5 | +date: 09.04.2024 |
| 6 | +--- |
| 7 | + |
| 8 | +## Rundown |
| 9 | + |
| 10 | +Barriers are a classic case of something dota has had introduced without being fully exposed to the modding API, so I've taken it upon myself to create a generic modifier class that can be inherited from in order to streamline barrier creation, and add some extra functionality to barriers in general. |
| 11 | + |
| 12 | +Unfortunately, this won't add any functionality to any native barriers (Aphotic Shield, Shield Rune, Pipe of Insight, the cheese upgrade no-one cared about a week after the update, etc) since this is for custom barriers. |
| 13 | + |
| 14 | +*Also uses the [Timers](https://github.com/ModDota/TypeScriptAddonTemplate/blob/master/src/vscripts/lib/timers.lua) library to avoid using the modifier's `OnIntervalThink()` function.* |
| 15 | +:::info |
| 16 | +[Link to the code](https://pastebin.com/M1E2zjku) |
| 17 | +::: |
| 18 | + |
| 19 | +### Important Info |
| 20 | +:::caution |
| 21 | +The base class uses some of the default modifier functions, so it's important to use the replacement functions instead! |
| 22 | +::: |
| 23 | +- `OnCreated()` → `OnBarrierCreated()` |
| 24 | +- `OnRefresh()` → `OnBarrierRefresh()` |
| 25 | +- `DeclareFunctions()` → `DeclareBonusFunctions()` |
| 26 | +- `AddCustomTransmitterData()` → `AddBonusCustomTransmitterData()` |
| 27 | + |
| 28 | +**These 4 functions behave the same as their respective default counterpart, but *must* be used instead, otherwise it will overwrite the base class version.** |
| 29 | + |
| 30 | +There's also some properties that are used in the barrier class that shouldn't be overwritten, but they use `__name` naming so unless you plan on using that, it shouldn't be a problem. |
| 31 | + |
| 32 | +Also worth noting; |
| 33 | +Because there are 3 types of barrier, each one corresponds to a specific damage type. |
| 34 | +- `DAMAGE_TYPE_PHYSICAL` is used for physical damage barriers (the red one) |
| 35 | +- `DAMAGE_TYPE_MAGICAL` is used for magical damage barriers (the blue one) |
| 36 | +- `DAMAGE_TYPE_PURE` is used for all damage barriers (the gold one) |
| 37 | + |
| 38 | +When you set a damage barrier's damage type, make sure to use this list as a reference so you don't declare it incorrectly. |
| 39 | + |
| 40 | +With that out of the way; Here's where the fun stuff begins. |
| 41 | + |
| 42 | + |
| 43 | +## Added Functions |
| 44 | +| Function | Return | Description | |
| 45 | +|-------|-------|-------| |
| 46 | +| SetBarrierType( [DAMAGE_TYPES](https://moddota.com/api/#!/vscripts/DAMAGE_TYPES) ) | *nil* | Set the barrier to block the specified type of damage. | |
| 47 | +| GetBarrierType() | [DAMAGE_TYPES](https://moddota.com/api/#!/vscripts/DAMAGE_TYPES) | Declare / Get what kind of damage the barrier blocks. Physical, Magical, or Pure (all damage). | |
| 48 | +| SetBarrierMaxHealth( int ) | *nil* | Set the max health of the barrier. | |
| 49 | +| GetBarrierMaxHealth() | int | Declare / Get the max health of the barrier. | |
| 50 | +| SetBarrierHealth( int ) | *nil* | Set the current health of the barrier. | |
| 51 | +| GetBarrierHealth() | int | Get the current health of the barrier. | |
| 52 | +| GetBarrierInitialHealth() | int | Declare / Get the initial health of the barrier. Defaults to `GetBarrierMaxHealth()`. | |
| 53 | +| IsBarrier() | bool | Returns true if this modifier is a barrier. | |
| 54 | +| IsBarrierFor( [DAMAGE_TYPES](https://moddota.com/api/#!/vscripts/DAMAGE_TYPES) ) | bool | Checks whether the barrier will block the specified type of damage. If you're checking the damage block type, use this function. If you're changing the damage block type, use `SetBarrierType()`. If you're declaring the damage block type, use `GetBarrierType()`. | |
| 55 | +| OnBarrierDamagedFilter( event ) | bool | A filter for damage to the barrier. Return false if you want the unit to be damaged instead. Set the `event.damage` to `0` if you want no damage to be done at all. (Only called on server) | |
| 56 | +| IsPersistent() | bool | Whether the barrier modifier will persist when the health reaches 0. False by default. | |
| 57 | +| ShowOnZeroHP() | bool | Whether the barrier bar is visible at 0 hp. If this is false and the barrier is at 0 hp, `IsBarrier()` will return false. | |
| 58 | +| BarrierUpdate() | *nil* | Updates the barrier. Not recommended to be called manually since it should update itself, but if you find a situation where there is a discrepancy then try this. | |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | +## Okay but how do I use it? |
| 63 | + |
| 64 | +I'm glad you asked. |
| 65 | +1. To start off, [grab the code](https://pastebin.com/M1E2zjku) then paste it into a lua file you've put somewhere in your vscripts folder. That is your `modifier_generic_barrier` class file. It's important to remember where you put it, because you'll need to know what the path is. |
| 66 | +2. When you create a new modifier you want to be a barrier, make sure to `require("path/modifier_generic_barrier")` so that you have access to the modifier class. You only need to do this once per file, so if you have multiple barrier modifiers in one file then you don't need to require it for each one! |
| 67 | +3. Make sure your new modifier inherits the base class by using `modifier_name = class(modifier_generic_barrier)` |
| 68 | +4. Declare the barrier's type using `GetBarrierType()`, and it's max health using `GetBarrierMaxHealth()` |
| 69 | +5. Enjoy your barrier! |
| 70 | + |
| 71 | + |
| 72 | +## Indepth example |
| 73 | +That's all well and good, but here is a little more indepth example of how to implement it. |
| 74 | + |
| 75 | +Say we're creating a new modifier that we want to be a barrier, at the top of the file we'll have this: |
| 76 | +```lua |
| 77 | +require("modifier_generic_barrier") -- assuming the file is called 'modifier_generic_barrier' and is just inside the vscripts folder |
| 78 | +modifier_new_barrier = class(modifier_generic_barrier) -- our new modifier now inherits from the modifier_generic_barrier class |
| 79 | +``` |
| 80 | + |
| 81 | +We need to tell the barrier what type of damage it will block, so after that we'll put: |
| 82 | +```lua |
| 83 | +function modifier_new_barrier:GetBarrierType() |
| 84 | + return DAMAGE_TYPE_PHYSICAL -- barrier will block physical damage |
| 85 | +end |
| 86 | +``` |
| 87 | + |
| 88 | +And now we need to tell it how much health the barrier will have: |
| 89 | +```lua |
| 90 | +function modifier_new_barrier:GetBarrierMaxHealth() |
| 91 | + return self.barrier_hp -- how much health the barrier has |
| 92 | +end |
| 93 | +``` |
| 94 | + |
| 95 | +But wait a second, we used `self.barrier_hp` which isn't declared yet. So let's quickly do that: |
| 96 | +```lua |
| 97 | +function modifier_new_barrier:OnBarrierCreated() -- Use the new function! |
| 98 | + self.barrier_hp = self:GetAbility():GetSpecialValueFor("barrier_health") |
| 99 | +end |
| 100 | +``` |
| 101 | +Take note of the fact that we used `OnBarrierCreated()` instead of `OnCreated()`, since we didn't want to overwrite the base class' `OnCreated()` function. |
| 102 | + |
| 103 | +Now let's say we want the parent to have some bonus health regen while the barrier is active, so lets declare that real quick; |
| 104 | +```lua |
| 105 | +function modifier_new_barrier:OnBarrierCreated() |
| 106 | + self.barrier_hp = self:GetAbility():GetSpecialValueFor("barrier_health") |
| 107 | + |
| 108 | + self.health_regen = self:GetAbility():GetSpecialValueFor("health_regen") -- We can declare properties as usual |
| 109 | +end |
| 110 | + |
| 111 | +function modifier_new_barrier:DeclareBonusFunctions() -- Use the new function! |
| 112 | + return { |
| 113 | + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT |
| 114 | + } |
| 115 | +end |
| 116 | + |
| 117 | +function modifier_new_barrier:GetModifierConstantHealthRegen() |
| 118 | + return self.health_regen |
| 119 | +end |
| 120 | +``` |
| 121 | + |
| 122 | +So now we have health regen and a barrier, very convenient. But what if we add something else spicy to the mix... what if we want to regenerate mana based on the amount of damage blocked by the barrier? Is that possible? Yes! |
| 123 | + |
| 124 | +Using the `OnBarrierDamageFilter()` function, we can handle any barrier damage instances we want, just as we would normally, and since it's called after the damage is checked but before it goes through, we don't need to do any checks of our own. We just need to add the ratio of damage to mana we want in our created function and we can then use it as we please. |
| 125 | +```lua |
| 126 | +function modifier_new_barrier:OnBarrierCreated() |
| 127 | + self.barrier_hp = self:GetAbility():GetSpecialValueFor("barrier_health") |
| 128 | + self.health_regen = self:GetAbility():GetSpecialValueFor("health_regen") |
| 129 | + |
| 130 | + if IsServer() then -- we only need this on the server since our damage filter is server side only |
| 131 | + self.barrier_to_mana = self:GetAbility():GetSpecialValueFor("barrier_to_mana_percent")/100 |
| 132 | + end |
| 133 | +end |
| 134 | + |
| 135 | +function modifier_new_barrier:OnBarrierDamageFilter( event ) |
| 136 | + local mana = event.damage |
| 137 | + |
| 138 | + -- we need to make sure we don't gain more mana than we should if the damage will overkill the barrier |
| 139 | + local current_barrier = self:GetBarrierHealth() |
| 140 | + if current_barrier < event.damage then |
| 141 | + mana = event.damage - (event.damage - current_barrier) |
| 142 | + end |
| 143 | + |
| 144 | + mana = mana * self.barrier_to_mana |
| 145 | + |
| 146 | + self:GetParent():GiveMana( mana ) |
| 147 | + |
| 148 | + SendOverheadEventMessage( |
| 149 | + nil, -- sendToPlayer |
| 150 | + OVERHEAD_ALERT_MANA_ADD, -- messageType |
| 151 | + self:GetParent(), -- targetEntity |
| 152 | + mana, -- value |
| 153 | + nil -- sourcePlayer |
| 154 | + ) |
| 155 | + |
| 156 | + return true -- Make sure to return true! |
| 157 | +end |
| 158 | +``` |
| 159 | + |
| 160 | +Now we have a physical damage barrier that gives us health regen, and grants us mana based on the damage we take. |
| 161 | +In all, our new modifier file should look something like this: |
| 162 | +```lua |
| 163 | +require("modifier_generic_barrier") |
| 164 | +modifier_new_barrier = class(modifier_generic_barrier) |
| 165 | + |
| 166 | +function modifier_new_barrier:GetBarrierType() |
| 167 | + return DAMAGE_TYPE_PHYSICAL |
| 168 | +end |
| 169 | + |
| 170 | +function modifier_new_barrier:GetBarrierMaxHealth() |
| 171 | + return self.barrier_hp |
| 172 | +end |
| 173 | + |
| 174 | +function modifier_new_barrier:OnBarrierCreated() |
| 175 | + self.barrier_hp = self:GetAbility():GetSpecialValueFor("barrier_health") |
| 176 | + self.health_regen = self:GetAbility():GetSpecialValueFor("health_regen") |
| 177 | + |
| 178 | + if IsServer() then |
| 179 | + self.barrier_to_mana = self:GetAbility():GetSpecialValueFor("barrier_to_mana_percent")/100 |
| 180 | + end |
| 181 | +end |
| 182 | + |
| 183 | +function modifier_new_barrier:DeclareBonusFunctions() |
| 184 | + return { |
| 185 | + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT |
| 186 | + } |
| 187 | +end |
| 188 | + |
| 189 | +function modifier_new_barrier:GetModifierConstantHealthRegen() |
| 190 | + return self.health_regen |
| 191 | +end |
| 192 | + |
| 193 | +function modifier_new_barrier:OnBarrierDamageFilter( event ) |
| 194 | + local mana = event.damage |
| 195 | + |
| 196 | + local current_barrier = self:GetBarrierHealth() |
| 197 | + if current_barrier < event.damage then |
| 198 | + mana = event.damage - (event.damage - current_barrier) |
| 199 | + end |
| 200 | + |
| 201 | + mana = mana * self.barrier_to_mana |
| 202 | + |
| 203 | + self:GetParent():GiveMana( mana ) |
| 204 | + |
| 205 | + SendOverheadEventMessage( |
| 206 | + nil, -- sendToPlayer |
| 207 | + OVERHEAD_ALERT_MANA_ADD, -- messageType |
| 208 | + self:GetParent(), -- targetEntity |
| 209 | + mana, -- value |
| 210 | + nil -- sourcePlayer |
| 211 | + ) |
| 212 | + |
| 213 | + return true |
| 214 | +end |
| 215 | +``` |
| 216 | + |
| 217 | +There we have it! A completely new barrier modifier using the base class. Pretty simple right? I hope so, that's the whole point of using the base class after all. |
| 218 | + |
| 219 | +:::note |
| 220 | +There's a few functions I didn't go over in this guide, but the code has small descriptions for the major ones, and any function that doesn't have a description can be understood from its name. |
| 221 | +::: |
| 222 | + |
| 223 | +:::info |
| 224 | +[Here's the code again](https://pastebin.com/M1E2zjku) |
| 225 | +::: |
0 commit comments