| 12-29-2008, 01:58 PM | #1 |
A few months ago I tinkered with making Zombies that reanimate when killed, and then figured out how to make them spread a zombifying disease and decay when they reanimate. I succeeded, whoo, then grew bored and engaged reality. Anyway, a few weeks ago I returned to scripting. and after looking over my work realized how messy and inefficient it was. I've been retooling since then. I dropped the decay, added scripted damage (instead of ability-caused damage) and have housed the mess in a nice library with reconfigurable constants. Now for the sake of efficiency and my further education, I invite a proof-read of the code. Obviously, I welcome constructive criticism and suggestions for additions (or subtractions). UPDATE: Replaced the old with the new. JASS://=========================================================================== // Plague Zombie //=========================================================================== library PlagueZombie requires TimerUtils //=========================================================================== // Global Declarations and Configuration //=========================================================================== globals private gamecache Cache = null private constant string PLAYER_STR = "PlagueSourcePlayer" private constant boolean CAUSES_DAMAGE = true // Damage received from PLAGUE_BUFF (def: true) private constant boolean CAUSES_REANIMATE = true // Reanimate from PLAGUE_BUFF (def: true) private constant boolean CAUSES_TRANSFORM = true // Reanimate as PLAGUE_REANIMATE_UNIT (def: true) private constant boolean UNTRANS_PLAGUE = true // Untransformed reanimated units gain PLAGUE_ABILITY (def: true) private constant boolean TIMED_LIFE = false // Reanimated units have timed life (def: false) private constant boolean USES_FOOD = false // Reanimated units use food (def: false) private constant integer PLAGUE_ABILITY = 'AXcu' // Ability rawcode which assigns PLAGUE_BUFF on attack (MUST ASSIGN) private constant integer PLAGUE_BUFF = 'BXcu' // Ability rawcode required for Plague effects (MUST ASSIGN) private constant integer REANIMATE_UNIT = 'nzom' // Unit rawcode for transformed reanimated units (def: 'nzom') private constant integer TIMED_LIFE_BUFF = 'BUan' // Buff rawcode for reanimated unit timed life (def: 'BUan') private constant real DMG_AMOUNT = 3.00 // Damage per interval for Plague effects (def: 3.00) private constant real DMG_INTERVAL = 0.3 // Game-time seconds between damage (def: 0.3) private constant real REANIMATE_DELAY = 10.0 // Game-time seconds from death to reanimation (def: 10.0) private constant real TIMED_LIFE_DURATION = 300.0 // Game-time seconds for reanimated unit timed life (def: 300.0) private constant real INFECT_PROB = 1.00 // Probability to infect (def: 1.00) private constant real REANIMATE_PROB = 1.00 // Probability to reanimate (def: 1.00) private constant real TRANSFORM_PROB = 0.50 // Probability to transform PLAGUE_REANIMATION_UNIT (def: 1.00) private constant string PLAGUE_SFX = "Units\\Undead\\PlagueCloud\\PlagueCloudtarget.mdl" private constant string PLAGUE_SFX_ATTACH = "head" private constant string REANIMATE_SFX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" private constant string REANIMATE_SFX_ATTACH = "origin" private constant string REANIMATE_DELAY_SFX = "Environment\\BlightDoodad\\BlightDoodad.mdl" public trigger DamageTrig = null public trigger ReanimateTrig = null endglobals //=========================================================================== // Get Random Probability private function GetRandomProb takes nothing returns real return GetRandomReal(0,1) endfunction // Handle to Integer private function H2I takes handle h returns integer return h return 0 endfunction // Handle to Key String private function H2K takes handle h returns string return I2S(H2I(h)) endfunction // Safe Wait with TimerUtils private function SafeWait takes real duration returns nothing local timer t local real timeRemaining if (duration > 0) then set t = NewTimer() call TimerStart(t, duration, false, null) loop set timeRemaining = TimerGetRemaining(t) exitwhen timeRemaining <= 0 call TriggerSleepAction(0.10) endloop call ReleaseTimer(t) endif endfunction //=========================================================================== // Plague Damage //=========================================================================== scope Damage public function TrigCondition takes nothing returns boolean local unit u = GetTriggerUnit() // Return false if the attacking unit doesn't have the PLAGUE_ABILITY, if the target already has the PLAGUE_BUFF, or if the unit is undead, mechanical, a structure, or hero. if (not ( GetUnitAbilityLevel( GetAttacker(), PLAGUE_ABILITY ) > 0 )) or (not ( GetUnitAbilityLevel( u, PLAGUE_BUFF ) == 0 )) or (not ((IsUnitType( u, UNIT_TYPE_UNDEAD) == false) and (IsUnitType( u, UNIT_TYPE_MECHANICAL) == false) and (IsUnitType( u, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType( u, UNIT_TYPE_HERO) == false))) then set u = null return false endif set u = null // Check probability of infection if ( not ( GetRandomProb() <= INFECT_PROB )) then return false endif return true endfunction public function TrigAction takes nothing returns nothing local unit u = GetTriggerUnit() local effect e = AddSpecialEffectTarget( PLAGUE_SFX, u, PLAGUE_SFX_ATTACH ) call UnitAddAbility( u, PLAGUE_BUFF ) call StoreInteger( Cache, PLAYER_STR, H2K(u), GetPlayerId(GetOwningPlayer(GetAttacker())) ) loop call UnitDamageTarget( u, u, DMG_AMOUNT, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DISEASE, WEAPON_TYPE_WHOKNOWS ) if ( GetUnitAbilityLevel(u, PLAGUE_BUFF ) <= 0 ) then call FlushStoredInteger( Cache, PLAYER_STR, H2K(u) ) exitwhen ( GetUnitAbilityLevel(u, PLAGUE_BUFF ) <= 0 ) endif exitwhen ( GetWidgetLife( u ) <= 0 ) call SafeWait( DMG_INTERVAL ) endloop call DestroyEffect( e ) set u = null set e = null endfunction endscope //=========================================================================== // Plague Reanimation //=========================================================================== scope Reanimate public function TrigCondition takes nothing returns boolean local unit u = GetTriggerUnit() // Return false if the unit doesn't have the PLAGUE_BUFF, or if the unit is flying or summoned. if (( not (GetUnitAbilityLevel( u, PLAGUE_BUFF ) >= 1)) or ( not (IsUnitType( u, UNIT_TYPE_FLYING) == false) and (IsUnitType( u, UNIT_TYPE_SUMMONED) == false))) then set u = null return false endif set u = null // Check the probability of reanimation. if ( not (GetRandomProb() <= REANIMATE_PROB)) then return false endif return true endfunction private function TransformCondition takes nothing returns boolean local unit u // Return false if CAUSES_TRANSFORM is false. if CAUSES_TRANSFORM == false then return false endif set u = GetTriggerUnit() // Return true if the unit is neither melee nor ranged (and thus cannot attack). if (IsUnitType( u, UNIT_TYPE_MELEE_ATTACKER) == false and IsUnitType( u, UNIT_TYPE_RANGED_ATTACKER) == false) then set u = null return true endif set u = null // Check the probability of transformation. if not ( GetRandomProb() <= TRANSFORM_PROB ) then return false endif return true endfunction public function TrigAction takes nothing returns nothing local unit u = GetTriggerUnit() local unit z = null local effect fx = null local player p = Player(GetStoredInteger( Cache, PLAYER_STR, H2K(u) )) local real x = GetUnitX(u) local real y = GetUnitY(u) local real f = GetUnitFacing(u) set fx = AddSpecialEffect( REANIMATE_DELAY_SFX, x, y ) call SetUnitInvulnerable( u, true ) call SafeWait( REANIMATE_DELAY ) if ( TransformCondition() == true ) then set z = CreateUnit( p, REANIMATE_UNIT, x, y, f) else set z = CreateUnit( p, GetUnitTypeId( u ), x, y, f) if ( UNTRANS_PLAGUE == true ) then call UnitAddAbility( z, PLAGUE_ABILITY ) endif call UnitAddType( z, UNIT_TYPE_UNDEAD) call SetUnitVertexColor( z, 125, 255, 125, 255 ) endif if ( TIMED_LIFE == true ) then call UnitApplyTimedLife( z, TIMED_LIFE_BUFF, TIMED_LIFE_DURATION ) endif call SetUnitUseFood( z, USES_FOOD ) call DestroyEffect( fx ) set fx = AddSpecialEffectTarget( REANIMATE_SFX, z, REANIMATE_SFX_ATTACH ) call ShowUnit(u, false) call FlushStoredInteger( Cache, PLAYER_STR, H2K(u) ) call SafeWait( 2.0 ) call DestroyEffect( fx ) set u = null set z = null set fx = null endfunction endscope //=========================================================================== // Initializer //=========================================================================== function InitTrig_PlagueZombie takes nothing returns nothing local integer i = 0 set Cache = InitGameCache("PlagueZombieCache") // Damage if ( CAUSES_DAMAGE == true ) then set DamageTrig = CreateTrigger() loop call TriggerRegisterPlayerUnitEvent( DamageTrig, Player(i), EVENT_PLAYER_UNIT_ATTACKED, null) set i = i + 1 exitwhen i == 16 endloop call TriggerAddCondition( DamageTrig, Condition(function Damage_TrigCondition) ) call TriggerAddAction( DamageTrig, function Damage_TrigAction ) endif // Reanimate if ( CAUSES_REANIMATE == true ) then set ReanimateTrig = CreateTrigger() set i = 0 loop call TriggerRegisterPlayerUnitEvent( ReanimateTrig, Player(i), EVENT_PLAYER_UNIT_DEATH, null) set i = i + 1 exitwhen i == 16 endloop call TriggerAddCondition( ReanimateTrig, Condition(function Reanimate_TrigCondition) ) call TriggerAddAction( ReanimateTrig, function Reanimate_TrigAction ) endif endfunction endlibrary |
| 12-29-2008, 02:26 PM | #2 |
For one thing, you can condense your big ole conditions into one line ones: JASS:
return GetUnitAbilityLevel( GetAttacker(), PLAGUE_ABILITY ) > 0 or GetUnitAbilityLevel( GetTriggerUnit(), PLAGUE_BUFF ) == 0 or IsUnitType(GetTriggerUnit(), UNIT_TYPE_UNDEAD) == false or .....
You get the point. Also, to avoid all those GetTriggerUnit() function calls you might want to just set a global for just this condition, that way at the beginning of your condition you set it and the one-line condition runs without all those extra GetTriggerUnit()'s. Just an idea. Also, use GetWidgetLife() instead of GetUnitState, I think it's just more efficient and easier to write. I'll post this now and take another look. EDIT: And I just noticed you were using waits in a short interval, within a loop. I'd recommend using timers for this, more specifically TimerUtils. It will associate that player ID you want to the timer and be MUCH more efficient, safe, and precise than using TriggerSleepAction. |
| 12-29-2008, 02:37 PM | #3 |
You can use only one trigger in the init part: JASS:function InitTrig_PlagueZombie takes nothing returns nothing local trigger t = null //local trigger trigReanimate = null local integer i = 0 set Cache = InitGameCache("PlagueZombieCache") // Damage if ( CAUSES_DAMAGE == true ) then set t = CreateTrigger() loop call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, null) set i = i + 1 exitwhen i == 12 endloop call TriggerAddCondition( t, Condition(function Damage_TrigCondition) ) call TriggerAddAction( t, function Damage_TrigAction ) endif // Reanimate if ( CAUSES_REANIMATE == true ) then set t = CreateTrigger() // By doing this, you're referencing to a new different trigger set i = 0 loop call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null) set i = i + 1 exitwhen i == 12 endloop call TriggerAddCondition( t, Condition(function Reanimate_TrigCondition) ) call TriggerAddAction( t, function Reanimate_TrigAction ) endif // End set t = null //set trigReanimate = null endfunction * In the function TrigAction you don't need to null the player variable, player handles are constant data and null the variable is useless. Hmm, you're using Kattana's handle vars, you can replace it with Table which allow a shorter syntax. |
| 12-29-2008, 05:45 PM | #4 | ||
Quote:
GetWidgetLife replaces GetUnitState. TriggerSleepAction replaced with TimerUtils and private function SafeWait: JASS:// Safe Wait with TimerUtils private function SafeWait takes real duration returns nothing local timer t local real timeRemaining if (duration > 0) then set t = NewTimer() call TimerStart(t, duration, false, null) loop set timeRemaining = TimerGetRemaining(t) exitwhen timeRemaining <= 0 call TriggerSleepAction(0.10) endloop call ReleaseTimer(t) endif endfunction Quote:
Initial local trigger made singular as per your suggestion. I thought, since player extends handle, that the pointer would not be autocleaned and would leak. Am I mistaken? And I'm not using Kattana's handle vars. Not that I'm aware of anyway. EDIT: Update the script in the first post. |
| 12-29-2008, 07:15 PM | #5 |
I like the way you remade the condition. Also, instead of writing SomeCondition == false or SomeConditiion == true You can write SomeCondition or not SomeCondition Player does extend handle but because every player handle is permanent there is no need to force the handle count to decrement, it will never be destroyed. I still think it would be a good idea to use TimerUtils more completely and use a structure, and associate structure instances to a timer. The timer would run periodically and reference that structural instance on each iteration. Really up to you though, especially since you are only damaging. I try to avoid waits, despite their ease of use and simplicity EDIT: I think Moyack means gamecache usage in general. If you are going to use gamecache, I would recommend Table like he said. |
| 12-30-2008, 09:36 PM | #6 |
I've been debugging the conditions. During tests, the infection would only spread to humans (read: Damage_TrigCondition was returning false). Which is difficult to comprehend, since I don't perform a race check. EDIT 2: The triggers weren't getting registered for Neutral Hostile and other neutral players. Fixed. Thanks for the information on the player handles. That's one less line of code. I've not to date written a struct before, so I'm a bit nervous. I shall need to look over other scripts to get an idea of how it ought to work. However, it was my understanding that TriggerSleepAction is safe when used in conjunction with a timer, since checking the timeRemaining avoids the problems otherwise encountered with dual-processors. That said, I would still like to learn how to use periodic timers to reference struct instances. I've considered using globals to store the two triggers, since a player may want to disable one or the other during the course of a game. I read about Table, but I do not immediately see it's use to this library. Since I only use the gamecache to store an integer for the player index, I think making this library require a whole other system would be superfluous. Are there any potential bugs or other dangers of which I ought to be aware that Table resolves? With each test, I debug the script (PlagueZombie is the only one giving me trouble so far) and debug the AI (both players run smoothly, but stupidly), both of which are JASS. This effectively doubles the time between my updates. I should have something to show within about an hour. EDIT: New Damage_TrigCondition JASS:public function TrigCondition takes nothing returns boolean local unit u = GetTriggerUnit() // Return false if the attacking unit doesn't have the PLAGUE_ABILITY, if the target already has the PLAGUE_BUFF, or if the unit is undead, mechanical, a structure, or hero. if (not ( GetUnitAbilityLevel( GetAttacker(), PLAGUE_ABILITY ) > 0 )) or (not ( GetUnitAbilityLevel( u, PLAGUE_BUFF ) == 0 )) or (not ((IsUnitType( u, UNIT_TYPE_UNDEAD) == false) and (IsUnitType( u, UNIT_TYPE_MECHANICAL) == false) and (IsUnitType( u, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType( u, UNIT_TYPE_HERO) == false))) then set u = null return false endif set u = null // Check probability of infection if ( not ( GetRandomProb() <= INFECT_PROB )) then return false endif return true endfunction EDIT 3: Added settings for timed life and food use, set triggers to global values. EDIT 4: Updated the first post again. One issue still looms that I shall need to address. The damage trigger fires when a unit is attacked, but before it is hit by the attack. This is silly when projectiles and missing is involved. |
| 12-30-2008, 10:09 PM | #7 |
I think you can use Anitarf's Adamage for that: http://wc3campaigns.net/showthread.php?t=102079 Or some attack detection system. Also, for going into vJASS and structs in particular, Vexorian has a good tutorial for struct basics. |
