| 06-15-2009, 10:31 PM | #1 |
Hi guys, I made a spell, a ward that raises dead bodies from the ground. Sure I could've used the Necro ability, but if I can code something and make it look better, with less object editor data, then why not ? I am looking for suggestions on how to improve the code ... and the effect ! =P This will be used for a caster, so I took special care with the efficiency (big battles -> mass wards). JASS://=========================================================================== //Summons a ward that will summon a skelly from a corpse every 2 seconds. The ward //can also carry an ability, which allows him to summon stronger skellies, depending //on it's level. A ward can only summon 1 unit at a time. // //@Requires: // - TimedLoop // - TimedHandles // //@author Flame_Phoenix // //@Credits: // - Vexorian, for TimedLoop // - TriggerHappy187, for TimedHandles // - Spec, for helping a lot on the making of the spell // - 0zyx0, for a cool suggestion on how to improve speed // //@version 1.5.1 //=========================================================================== scope SoulWard2 initializer Init private keyword skelies //needed for core, do not change //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant integer WARD_ID = 'o00H' //id of the ward private constant integer WARD_ABI = 'A01F' //ability that tells us the level of the ward private constant string BOLT = "AFOD" //lightning effect private constant string SUMMON_EFF = "Abilities\\Spells\\Undead\\RaiseSkeletonWarrior\\RaiseSkeleton.mdl" //summon effect private constant real COOLDOWN = 2. //cooldown between rays of summoning endglobals private constant function Radius takes integer level returns real //the radius of the ward return 450. endfunction private constant function SkelyDuration takes integer level returns real //amount of time each skely lasts return 30. endfunction private function SetSkelies takes nothing returns nothing //skelies that can be summoned by the ward. The number in the array is the //level they ar asociated with set skelies[1] = 'nsko' //skeletal orc set skelies[2] = 'nsoc' //skeletal orc champion endfunction private function PickVictims takes unit ward, unit victim returns boolean //the units the ward can affect return (GetWidgetLife(victim) < 0.405) and (IsUnitTypeDummy(victim)==false) and (IsUnitTypeWard(victim) == false) endfunction //=========================================================================== //=============================SETUP END===================================== //=========================================================================== globals private integer array skelies private group wards private boolexpr chooseWards private group dead private boolexpr chooseDead private unit tmpWard endglobals //=========================================================================== private function PickDead takes nothing returns boolean return PickVictims(tmpWard, GetFilterUnit()) endfunction //=========================================================================== private function PickWards takes nothing returns boolean return GetUnitTypeId(GetFilterUnit()) == WARD_ID endfunction //=========================================================================== private struct aSoulWard unit theWard real wardX real wardY real counter //method required by TimedLoop private method onTimedLoop takes nothing returns boolean local integer wardLevel local unit aVictim local real vicX local real vicY local unit skely //if the ward is dead, there is no need to go on, we just clean all mess //TimedLoop we call onDestroy for us! if GetWidgetLife(.theWard) <= 0.405 then return TimedLoop_STOP endif //If it is time to summon skelies, we summon them. If it is not time yet, we simply //increase the counter! if .counter >= COOLDOWN then //time to pick dead units near the current ward set wardLevel = GetUnitAbilityLevel(.theWard, WARD_ABI) set tmpWard = .theWard call GroupEnumUnitsInRange(dead, .wardX, .wardY, Radius(wardLevel), chooseDead) //if the first unit of the group is null, then it means the group has 0 units, and //it means we don't need to do anything at all xD if FirstOfGroup(dead) != null then set aVictim = GroupPickRandomUnit(dead) set vicX = GetUnitX(aVictim) set vicY = GetUnitY(aVictim) //create the bolt and the skelie! call DestroyLightningDelayed(AddLightningEx(BOLT, false, .wardX, .wardY, 50., vicX, vicY, -1.), 1.) set skely = CreateUnit(GetOwningPlayer(.theWard), skelies[wardLevel], vicX, vicY, 0.) call SetUnitAnimation(skely, "birth") call UnitApplyTimedLife(skely, 'BTLF', SkelyDuration(wardLevel)) //a nice effect call DestroyEffect(AddSpecialEffect(SUMMON_EFF, vicX, vicY)) //remove the body of the victim call ShowUnit(aVictim, false) call RemoveUnit(aVictim) //cleaning up the group for the next ward =D call GroupClear(dead) endif //now that we did our job in the correct time, we reset the timer, for the cooldown to start //again once more =D set .counter = 0 //cleaning our skely set skely = null else set .counter = .counter + TimedLoop_PERIOD endif //if everything was Ok, we just keep going ... return TimedLoop_CONTINUE endmethod implement TimedLoop //This does the module magic static method create takes unit ward returns aSoulWard local aSoulWard sw = aSoulWard.allocate() set sw.theWard = ward set sw.wardX = GetUnitX(ward) set sw.wardY = GetUnitY(ward) set sw.counter = 0 //The TimedLoop script works by // creating a startTimedLoop method that will // do all the dirty work and end up calling // .onTimedLoop... call sw.startTimedLoop() return sw endmethod endstruct //=========================================================================== private function Conditions takes nothing returns boolean local aSoulWard ward if GetUnitTypeId(GetSummonedUnit()) == WARD_ID then set ward = aSoulWard.create(GetSummonedUnit()) endif return false endfunction //=========================================================================== private function Init takes nothing returns nothing local trigger SoulWardTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(SoulWardTrg, EVENT_PLAYER_UNIT_SUMMON ) call TriggerAddCondition(SoulWardTrg, Condition(function Conditions)) //setting globals set wards = CreateGroup() set chooseWards = Condition(function PickWards) set dead = CreateGroup() set chooseDead = Condition(function PickDead) call SetSkelies() //Preloading effects call Preload(SUMMON_EFF) endfunction endscope Btw, is there a way to remove a corpse without using RemoveUnit ??? |
| 06-16-2009, 06:59 AM | #2 |
You don't use any TSA within the actions, so they could be conditions instead, to improve speed. You should also avoid using CountUnitsInGroup(). Since the group is not needed for a longer time, you can use FirstOfGroup(dead) != null. |
| 06-16-2009, 08:11 AM | #3 |
I think, the algorithm is not fully correct. Let's say, hero summoned two wards at 0.0 and 1.5 seconds of game time. They shouldn't trigger simultaneously (first ward should trigger at 2.0 - 4.0 - 6.0 sec, second ward should trigger at 3.5 - 5.5 - 7.5 sec), but, in your case, they will. I hope you got what I mean. How to fix? 1. Use single timer loop with tiny period. 2. Use new timer for each ward. (use, e.g., TimerUtils). Now about code efficiency... Using CountUnitsInGroup() smells a little bit lame, especially when you can count them in filter function: JASS:private function PickDead takes nothing returns boolean if PickVictims(tmpWard, GetFilterUnit()) then set temp_i = temp_i + 1 return true endif return false endfunction // . . . set temp_i = 0 call GroupEnumUnitsInRange(dead, GetUnitX(aWard), GetUnitY(aWard), Radius(wardLevel), chooseDead) if temp_i > 0 then // . . . endif You can go further and put all picking code into filter functions. What's wrong with removing corpses with RemoveUnit()? |
| 06-16-2009, 09:35 AM | #4 | |||||||
Quote:
Quote:
Quote:
Quote:
So my main idea would be to use a stack for the instances and 1 timer I guess .... however I had bad experiences with this method in the past, and I do not master it's use as I would like to =S Quote:
Quote:
Quote:
So the main idea is to avoid it whenever possible. I can create a thread about it, if you find necessary. EDIT EDIT Code updated to 1.4.3, now it applies 0zyx0 suggestion. rep++ for 0zyx0 and Spec for their suggestions and for being so awesome =P |
| 06-16-2009, 11:35 AM | #5 | |
Quote:
detects wards in range of the dying unit (and determines which ward should raise the unit if this is an issue... small details...); raises the corpse; ...instead of a regular corpse check + monitor all the wards + index them etc something like: JASS:function filter returns boolaen return canUnitBeRaised endfunction function wardFilter returns boolean return isThisAWard and isItSuitable endfunction function raise local group wards = CreateGroup() call GroupEnumUnitsInRange(wards, *maxWardRange(cozThisMightChangePerLevelOfTheWardAbility), function filter) //pick which ward should raise the unit (the closest one? the oldest one? the highest-level one? etc) //raise the unit //flush endfunction function Init local trigger t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(t, Condition(filter) call TriggerAddAction(t, raise) set t = null endfunction |
| 06-16-2009, 11:36 AM | #6 | |||
Quote:
As far as I know, moderators require users' scripts to be maximally modular (and efficient too, of course). TimedLoop is probably the thing you should to use. Quote:
It is usual temporary variable application, and it doesn't affect MUI possibility. Quote:
|
| 06-16-2009, 11:43 AM | #7 | |||
Quote:
Quote:
I will try using right away ! Quote:
I will try using TimedLoop. If I succeed, you are sure to have your name on Credits Spec =D |
| 06-16-2009, 11:58 AM | #8 |
to raise spawned skeles too? also, where can i look up this "phantom handle"-group thing? |
| 06-16-2009, 01:08 PM | #9 |
Groups - Memory Leaks and Slowdowns - actually, not stated as phantom handle issue, but it is identical. By the way, I use "phantom handle" definition for any handle, that is referenced to non-existing (or, maybe, recreated) object. But hidden unit is normally removed from the game, when his decay animation is finished, isn't he? |
| 06-16-2009, 01:50 PM | #10 | ||
Quote:
http://www.wc3c.net/showthread.php?p...47#post1089647 Quote:
Anyway, the spell was updated to version 1.5.0, it now uses TimedLoop for super efficiency! I am open to suggestions and comments ! Thx to Spec for suggesting the system =D |
| 06-17-2009, 02:32 PM | #11 |
Small precision tip: JASS:if GetWidgetLife(.theWard) < 0.405 then return TimedLoop_STOP endif I think PickVictims() function has a lot of unneeded stuff. As far as I know, standard structures, flying units, heroes, mechanical units, summons doesn't create corpses after death. JASS:
private function PickVictims takes unit ward, unit victim returns boolean
//the units the ward can affect
return (GetWidgetLife(victim) < 0.405) and (IsUnitTypeDummy(victim)==false) and (IsUnitTypeWard(victim)==false)
endfunctionOh, and I don't see IsUnitTypeDummy and IsUnitTypeWard functions... I guess, you just haven't wrote them in your post. |
| 06-17-2009, 06:31 PM | #12 | |||
Quote:
Quote:
Quote:
Thx you have been of great aid =D I have, however, a small problem with TimedLoop ... you see, I have to wait .25 seconds to take care of the struct and stop it's effect. In this case it is not a problem .... but let's imagine the following case: - I have a super channel spell with many effects - The user is an idiot, and changes the time to 10 seconds, instead of .25 So, if my hero dies ... the effects of the spell with only die 10 seconds after! Is there a way to fix this? Btw, more info at: http://www.wc3c.net/showthread.php?t=105515&page=4 I know I am spamming help, but having in mind you know a lot of TimedLoop .. =P |
| 06-18-2009, 09:53 AM | #13 | |
Quote:
In other words - you shouldn't take care about that. 'nuff said. |
| 06-18-2009, 12:17 PM | #14 | |
Quote:
|
