- Joined
- Aug 1, 2013
- Messages
- 4,658
Hi all.
This is something that might be very interesting for all of you but I think it is quite a bit harder to make as I originally expected.
Goal:
Everything needs something to do right?
I want a system that simulates basic attacks. It is almost exactly the opposite of a DDS, this one will tell you when you want to apply the damage of a basic attack.
Why?
There are some features that you can have when using something like this.
First of all, you dont need the Spell Damage Reduction trick to find out if something is spell damage in a DDS because you know that all basic attack damage is done by this system.
Next to that, you can remove attack speed limitations... bonus attack speed of 2,000% proves to be very stupid cause the animations arent fast enough, and bonus attack speed may never be 0 or lower (you understand if you know the formula).
Also, attack range can simply be set using ability levels. (This does mean that there will be a lot of ability levels for this system.)
Last but not least, you can force attacks! I mean like all those people that want a proper taunt spell that you CANNOT stop by abusing the "stop" (anything else works as well ofc) order.
Code:
The code is quite simple:
It uses TimerUtils and Bribe's Unit Indexer (I modified it a bit so I can choose when it initializes for example... we will get to that later).
First of all, we have to store data for each unit (damage point, backswing point, etc)
So we need a trigger that runs when a unit is created:
We need the following information for each unit:
- Backswing Point
- Damage Point
- Cooldown
- Range
- Attack Speed (standard value)
- IsForced (will come later)
- WeaponSound
The unit also needs the ability that we use to attack units with.
And it will be set to the range of the unit.
When a unit is attacked, we re-order them to use the ability.
This will make all units unable to use their normal attack and use the ability instead with no delay.
For some weird reason, I have to add a 0s timer because when you cast an ability with lower range than the current distance, it just stops the unit.
Anyway, we reorder the unit to cast the spel.
Now when the unit casts the spell, we have to attack in different phases.
1, We swing our weapon at the target.
2, We draw our weapon back from the target.
3, We wait until we can attack again.
(Between 1 and 2, we apply the damage.)
1: Ability Channel Event:
If the casting ability is our attack ability, we assing the target and damage on the id of the source.
We also start a timer that runs for the Damage Point duration divided by the bonus attack speed and we set the animation speed of the source to the bonus attack speed. (You can see that this may not be 0.)
2: When that timer expires:
If the order of the unit has changed in the meantime, this timer is no longer accurate any more and has to be removed.
If he unit has ordered the same ability, we have handled that in phase 1.
Here we call a configurable function called BA_OnBasicAttack() which will apply the damage for us.
We also start the timer again for the Backswing Point.
3: When that timer expires:
Here we replace the attack ability with the cooldown ability (stand ready).
The attack actions will now cast this ability instead and if the unit was busy casting the attack ability, he will now cast the attack cooldown ability.
Ofcourse the range has to fit.
Now the timer will run for the remaining time for the cooldown.
Cooldown reset:
Same as before, but now the other way around.
Other stuff:
With this, you can ofcourse have functions like SetUnitAttackRange() and SetUnitAttackSpeed() which have both been added to the system.
There are 2 drawbacks from this system.
You cannot use Object Editor damage and attack speed any more.
But we never use that anyway right?
To replace these, we have to use:
We both load those values from out stat system... and if you dont use one yet, then get started on REAL maps.
This one is slightly different:
As you could see, the system would only work for melee units... wait, we always wanted that!
You as map maker now can change attack type and damage type of your basic attacks.
I have no need for that so I didnt bother to add Attack Type and Damage Type to the system.
Ranged units should have their basic attack missile be created here.
You can use a system that uses Acid Bomb as a missile if you really want to stick to WC3 standards.
Why this thread?
There is something that this system does not correctly...
Attack-Once
Attack-Move
I have added a new version: Attack-Force which is basically an attack you cannot stop.
I have to know how I can add Attack-Once and Attack-Move in this system without making it a pain or inefficient.
Ofcourse credits will be given and rep as well... and you will be a hero in my AoS
I thought of something like adding functions like:
function OrderAttack takes unit whichUnit, unit target returns boolean
function OrderAttackForce takes unit whichUnit, unit target returns boolean
function OrderAttackOnce takes unit whichUnit, unit target returns boolean
function OrderAttackMove takes unit whichUnit, real targetX, real targetY returns boolean
But how those would be implemented is something I dont really see.
Ofcourse any help and comments are appreciated.
Testmap:
I have added a tesmap with the system and an example to show its possibilities so far.
Type /setrange <int> to set the range to that value
Type /setspeed <int> to set the bonus attack speed to that value (remember that this is a factor of the base attack speed... it will be switched to real soon)
Type /taunt to make the attacks forced and unforced based on what it was. (try stopping the paladins attack)
(You have to have the Object Data Extractor and LUA channel ability hotfix installed (above JNGP 1.5d at least) to make you be able to modify the map.)
This is something that might be very interesting for all of you but I think it is quite a bit harder to make as I originally expected.
Goal:
Everything needs something to do right?
I want a system that simulates basic attacks. It is almost exactly the opposite of a DDS, this one will tell you when you want to apply the damage of a basic attack.
Why?
There are some features that you can have when using something like this.
First of all, you dont need the Spell Damage Reduction trick to find out if something is spell damage in a DDS because you know that all basic attack damage is done by this system.
Next to that, you can remove attack speed limitations... bonus attack speed of 2,000% proves to be very stupid cause the animations arent fast enough, and bonus attack speed may never be 0 or lower (you understand if you know the formula).
Also, attack range can simply be set using ability levels. (This does mean that there will be a lot of ability levels for this system.)
Last but not least, you can force attacks! I mean like all those people that want a proper taunt spell that you CANNOT stop by abusing the "stop" (anything else works as well ofc) order.
Code:
The code is quite simple:
It uses TimerUtils and Bribe's Unit Indexer (I modified it a bit so I can choose when it initializes for example... we will get to that later).
First of all, we have to store data for each unit (damage point, backswing point, etc)
So we need a trigger that runs when a unit is created:
JASS:
function AllocateUnitAttackData takes nothing returns boolean
local integer id = GetUnitUserData(udg_UDexUnits[udg_UDex])
local integer unitTypeId = GetUnitTypeId(udg_UDexUnits[id])
set attackBackswingPoint[id] = GetUnitTypeBackswingPoint1(unitTypeId)
set attackDamagePoint[id] = GetUnitTypeDamagePoint1(unitTypeId)
set attackCooldown[id] = GetUnitTypeCooldown1(unitTypeId)
set attackRange[id] = 100//GetUnitTypeRange1(unitTypeId)
set attackSpeed[id] = 1.
set isAttackForced[id] = false
set attackSound[id] = BA_GetWeaponSound(GetUnitTypeWeaponSound1(unitTypeId))
call UnitAddAbility(udg_UDexUnits[id], attackAbility)
call SetUnitAbilityLevel(udg_UDexUnits[id], attackAbility, R2I((attackRange[id]-100.) / 5) + 1)
return false
endfunction
- Backswing Point
- Damage Point
- Cooldown
- Range
- Attack Speed (standard value)
- IsForced (will come later)
- WeaponSound
The unit also needs the ability that we use to attack units with.
And it will be set to the range of the unit.
When a unit is attacked, we re-order them to use the ability.
This will make all units unable to use their normal attack and use the ability instead with no delay.
JASS:
function ReorderAttack takes nothing returns nothing
local integer id = GetTimerData(GetExpiredTimer())
call IssueTargetOrderById(udg_UDexUnits[id], attackOrder, reorderTarget[id])
call ReleaseTimer(reorderTimer[id])
set reorderTarget[id] = null
set reorderTimer[id] = null
endfunction
function AutoAttack takes nothing returns boolean
local integer id = GetUnitUserData(GetAttacker())
set reorderTarget[id] = GetTriggerUnit()
set reorderTimer[id] = NewTimerEx(id)
call TimerStart(reorderTimer[id], 0, false, function ReorderAttack)
return false
endfunction
Anyway, we reorder the unit to cast the spel.
Now when the unit casts the spell, we have to attack in different phases.
1, We swing our weapon at the target.
2, We draw our weapon back from the target.
3, We wait until we can attack again.
(Between 1 and 2, we apply the damage.)
1: Ability Channel Event:
JASS:
function OnAttack takes nothing returns boolean
local unit source
local integer id
local integer abilityId = GetSpellAbilityId()
if abilityId == attackAbility or abilityId == attackForceAbility then
set source = GetTriggerUnit()
set id = GetUnitUserData(source)
set newTarget[id] = GetSpellTargetUnit()
if (not attackHasLanded[id]) and newTarget[id] != attackTarget[id] then
if attackTimer[id] == null then
set attackTimer[id] = NewTimerEx(id)
endif
set attackDamage[id] = BA_GetUnitAttackDamage(source)
set attackTarget[id] = newTarget[id]
set attackHasLanded[id] = false
call SetUnitTimeScale(udg_UDexUnits[id], BA_GetUnitAttackSpeed(udg_UDexUnits[id]))
call TimerStart(attackTimer[id], attackDamagePoint[id] / BA_GetUnitAttackSpeed(udg_UDexUnits[id]), false, function AttackOnDamagePoint)
endif
set source = null
elseif abilityId == attackCooldownAbility then
set newTarget[GetUnitUserData(GetTriggerUnit())] = GetSpellTargetUnit()
endif
return false
endfunction
We also start a timer that runs for the Damage Point duration divided by the bonus attack speed and we set the animation speed of the source to the bonus attack speed. (You can see that this may not be 0.)
2: When that timer expires:
JASS:
function AttackOnDamagePoint takes nothing returns nothing
local integer id = GetTimerData(GetExpiredTimer())
if GetUnitCurrentOrder(udg_UDexUnits[id]) != attackOrder then
call ReleaseTimer(attackTimer[id])
set attackTimer[id] = null
set attackTarget[id] = null
return
endif
set attackHasLanded[id] = true
call BA_OnBasicAttack(id)
call TimerStart(attackTimer[id], attackBackswingPoint[id] / BA_GetUnitAttackSpeed(udg_UDexUnits[id]), false, function AttackOnCooldown)
endfunction
If he unit has ordered the same ability, we have handled that in phase 1.
Here we call a configurable function called BA_OnBasicAttack() which will apply the damage for us.
We also start the timer again for the Backswing Point.
3: When that timer expires:
JASS:
function AttackOnCooldown takes nothing returns nothing
local integer id = GetTimerData(GetExpiredTimer())
local boolean doesAttack = GetUnitCurrentOrder(udg_UDexUnits[id]) == attackOrder
call SetUnitTimeScale(udg_UDexUnits[id], 1)
if GetUnitAbilityLevel(udg_UDexUnits[id], attackForceAbility) > 0 then
call UnitRemoveAbility(udg_UDexUnits[id], attackForceAbility)
call UnitAddAbility(udg_UDexUnits[id], attackForceCooldownAbility)
call SetUnitAbilityLevel(udg_UDexUnits[id], attackForceCooldownAbility, R2I((attackRange[id]-100.) / 5) + 1)
else
call UnitRemoveAbility(udg_UDexUnits[id], attackAbility)
call UnitAddAbility(udg_UDexUnits[id], attackCooldownAbility)
call SetUnitAbilityLevel(udg_UDexUnits[id], attackCooldownAbility, R2I((attackRange[id]-100.) / 5) + 1)
endif
if doesAttack then
call IssueTargetOrderById(udg_UDexUnits[id], attackOrder, newTarget[id])
endif
call TimerStart(attackTimer[id], (attackCooldown[id] - attackDamagePoint[id] - attackBackswingPoint[id]) / BA_GetUnitAttackSpeed(udg_UDexUnits[id]), false, function AttackOnCooldownReset)
endfunction
The attack actions will now cast this ability instead and if the unit was busy casting the attack ability, he will now cast the attack cooldown ability.
Ofcourse the range has to fit.
Now the timer will run for the remaining time for the cooldown.
Cooldown reset:
JASS:
function AttackOnCooldownReset takes nothing returns nothing
local integer id = GetTimerData(GetExpiredTimer())
local boolean doesAttack = GetUnitCurrentOrder(udg_UDexUnits[id]) == attackOrder
if GetUnitAbilityLevel(udg_UDexUnits[id], attackForceCooldownAbility) > 0 then
call UnitRemoveAbility(udg_UDexUnits[id], attackForceCooldownAbility)
call UnitAddAbility(udg_UDexUnits[id], attackForceAbility)
call SetUnitAbilityLevel(udg_UDexUnits[id], attackForceAbility, R2I((attackRange[id]-100.) / 5) + 1)
else
call UnitRemoveAbility(udg_UDexUnits[id], attackCooldownAbility)
call UnitAddAbility(udg_UDexUnits[id], attackAbility)
call SetUnitAbilityLevel(udg_UDexUnits[id], attackAbility, R2I((attackRange[id]-100.) / 5) + 1)
endif
call ReleaseTimer(attackTimer[id])
set attackTimer[id] = null
set attackTarget[id] = null
set attackHasLanded[id] = false
set attackDamage[id] = 0
if doesAttack then
call IssueTargetOrderById(udg_UDexUnits[id], attackOrder, newTarget[id])
endif
endfunction
Other stuff:
With this, you can ofcourse have functions like SetUnitAttackRange() and SetUnitAttackSpeed() which have both been added to the system.
There are 2 drawbacks from this system.
You cannot use Object Editor damage and attack speed any more.
But we never use that anyway right?
To replace these, we have to use:
JASS:
function BA_GetUnitAttackDamage takes unit whichUnit returns real
return 10.
endfunction
function BA_GetUnitAttackSpeed takes unit whichUnit returns real
return attackSpeed[GetUnitUserData(whichUnit)]
endfunction
This one is slightly different:
JASS:
function BA_OnBasicAttack takes integer id returns nothing
if IsUnitType(udg_UDexUnits[id], UNIT_TYPE_RANGED_ATTACKER) then
//Create missile
else
call UnitDamageTarget(udg_UDexUnits[id], attackTarget[id], attackDamage[id], true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, weaponSound[attackSound[id]])
endif
endfunction
You as map maker now can change attack type and damage type of your basic attacks.
I have no need for that so I didnt bother to add Attack Type and Damage Type to the system.
Ranged units should have their basic attack missile be created here.
You can use a system that uses Acid Bomb as a missile if you really want to stick to WC3 standards.
Why this thread?
There is something that this system does not correctly...
Attack-Once
Attack-Move
I have added a new version: Attack-Force which is basically an attack you cannot stop.
I have to know how I can add Attack-Once and Attack-Move in this system without making it a pain or inefficient.
Ofcourse credits will be given and rep as well... and you will be a hero in my AoS
I thought of something like adding functions like:
function OrderAttack takes unit whichUnit, unit target returns boolean
function OrderAttackForce takes unit whichUnit, unit target returns boolean
function OrderAttackOnce takes unit whichUnit, unit target returns boolean
function OrderAttackMove takes unit whichUnit, real targetX, real targetY returns boolean
But how those would be implemented is something I dont really see.
Ofcourse any help and comments are appreciated.
Testmap:
I have added a tesmap with the system and an example to show its possibilities so far.
Type /setrange <int> to set the range to that value
Type /setspeed <int> to set the bonus attack speed to that value (remember that this is a factor of the base attack speed... it will be switched to real soon)
Type /taunt to make the attacks forced and unforced based on what it was. (try stopping the paladins attack)
(You have to have the Object Data Extractor and LUA channel ability hotfix installed (above JNGP 1.5d at least) to make you be able to modify the map.)