| 09-15-2008, 07:11 PM | #1 |
Hi guys, I have a problem with a spell I submitted, here -> confusion spell. Problem is that it bugs ! When you cast first time on the units, they change owner, get back, all works fine. If you cast 2nd time on 2nd units that were already victims ... it works correctly some times only, the victim actually looses the original player and never returns to him(only some times) ... weird question: If it malfunctions (it does) why doe the bug only happens some times, and not always ? I have a replay from Bobo_the_Kodo so you all can understand the example. Please some one help me with this ? JASS://=========================================================================== //A spell that selects all units inside the target AOE and confuses them, //changing their owner by an amount of time. // //Requires TimerUtils // //@author Flame_Phoenix // //@credits //- Rafael Br, the original creator of the spell and idea //- Anitarf, the guy with ideas about how to solve problems //- Litany, the only guys that helped me unbugging this spell, when all others didn't care //- My first teacher of vJASS: Blue_Jeans //- All other people I forgot or ignored // //@version 1.4 //=========================================================================== scope Confusion initializer Init //This will be needed for spell's core, don't change private keyword tmpPlayer private keyword PlayerArray private keyword playerLevelChances //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant integer AID = 'A003' //the rawcode of the ability private constant integer ARRAY_SIZE = 15 //the maximum number of players, Human player + computer players you use private constant integer MAX_LEVELS = 3 //the maximum amount of levels the ability will have private constant integer MAX_HUMAN_PLAYERS = 11 //the maximum number of human player you'll use private constant string EFFECT = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" endglobals private constant function Duration takes integer level returns real return 20. + (level * 10) //the duration of the spell on the units endfunction private constant function Radius takes integer level returns real return 125. + (level * 75) //the radius the spell will have endfunction private function PlayersCallibration takes nothing returns nothing local integer i local integer j //here we set all human players with a nice loop set i = 0 loop exitwhen(i == MAX_HUMAN_PLAYERS) set PlayerArray[i] = Player(i) set i = i + 1 endloop //here we set computer controled creeps manually set PlayerArray[12] = Player(PLAYER_NEUTRAL_AGGRESSIVE) set PlayerArray[13] = Player(bj_PLAYER_NEUTRAL_VICTIM) set PlayerArray[14] = Player(PLAYER_NEUTRAL_PASSIVE) //here we set the chances all human players have of controlling the units //with a nice loop to fill our 2D array set i = 1 loop exitwhen (i > MAX_LEVELS) set j = 0 loop exitwhen(j == MAX_HUMAN_PLAYERS) set playerLevelChances[i][j] = .0666 set j = j + 1 endloop set i = i + 1 endloop //if you prefer, you can avoid loops and set the levels and chances //manually, like I do in this example //I this case, computer controled players have more chances of getting //the units. set playerLevelChances[1][12] = .067 set playerLevelChances[1][13] = .067 set playerLevelChances[1][14] = .0668 set playerLevelChances[2][12] = .067 set playerLevelChances[2][13] = .067 set playerLevelChances[2][14] = .0668 set playerLevelChances[3][12] = .067 set playerLevelChances[3][13] = .067 set playerLevelChances[3][14] = .0668 //PS: Note that this takes a lot more text however xD endfunction private function Targets takes nothing returns boolean //the units that will be affected by the spell return IsUnitEnemy(GetFilterUnit(), tmpPlayer) and (GetWidgetLife(GetFilterUnit()) > 0.405) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false) endfunction //=========================================================================== //=============================SETUP END===================================== //=========================================================================== //small array type playerChances extends real array[ARRAY_SIZE] globals //BIG array private playerChances array playerLevelChances[MAX_LEVELS] private player array PlayerArray[ARRAY_SIZE] private group g private boolexpr b private player tmpPlayer endglobals //=========================================================================== private struct MyStruct unit caster integer level timer dur group array allPlayers[ARRAY_SIZE] static method create takes unit caster returns MyStruct local MyStruct data = MyStruct.allocate() local integer i //setting variables set data.caster = caster set data.level = GetUnitAbilityLevel(caster, AID) set data.dur = NewTimer() set i = 0 loop exitwhen (i == ARRAY_SIZE) set data.allPlayers[i] = CreateGroup() set i = i + 1 endloop return data endmethod method onDestroy takes nothing returns nothing local integer i = 0 loop exitwhen (i == ARRAY_SIZE) call DestroyGroup(.allPlayers[i]) set i = i + 1 endloop call ReleaseTimer(.dur) endmethod endstruct //=========================================================================== private function EndEffect takes nothing returns nothing //here we catch the timer local MyStruct data = MyStruct(GetTimerData(GetExpiredTimer())) local unit f local integer i //here we walk trhough the group array set i = 0 loop exitwhen(i == ARRAY_SIZE) //here we walk through every single unit inside the group loop set f = FirstOfGroup(data.allPlayers[i]) exitwhen(f == null) call GroupRemoveUnit(data.allPlayers[i], f) call SetUnitOwner(f, Player(i), true) endloop set i = i + 1 endloop call data.destroy() endfunction //=========================================================================== //RandomPlayer function made by Litany, adapted to spell by Flame_Phoenix //=========================================================================== private function RandomPlayer takes integer structure returns player local MyStruct data = structure local real chance = GetRandomReal(0, 1) local real playerChance = 0 local integer i = 0 loop exitwhen(i == ARRAY_SIZE ) set playerChance = playerChance + playerLevelChances[data.level][i] if chance <= playerChance then return Player(i) endif set i = i + 1 endloop return Player(0) endfunction //=========================================================================== private function Conditions takes nothing returns boolean return GetSpellAbilityId() == AID endfunction //=========================================================================== private function Actions takes nothing returns nothing local MyStruct data = MyStruct.create(GetTriggerUnit()) local location spellLoc = GetSpellTargetLoc() local real spellX = GetLocationX(spellLoc) local real spellY = GetLocationY(spellLoc) local unit f local integer i //here we select the units we will affect set tmpPlayer = GetOwningPlayer(data.caster) call GroupEnumUnitsInRange(g, spellX, spellY, Radius(data.level), b) //and now we change their owners loop set f = FirstOfGroup(g) exitwhen(f == null) call GroupRemoveUnit(g, f) //with this line we keep track of the original owner call GroupAddUnit(data.allPlayers[GetPlayerId(GetOwningPlayer(f))], f) //here we change its player call SetUnitOwner(f, RandomPlayer(data), true) call DestroyEffect(AddSpecialEffect(EFFECT, GetUnitX(f), GetUnitY(f))) endloop //start the timer call SetTimerData(data.dur, integer(data)) call TimerStart(data.dur, Duration(data.level), true, function EndEffect) call RemoveLocation(spellLoc) set spellLoc = null endfunction //=========================================================================== private function Init takes nothing returns nothing local integer i local trigger ConfusionTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(ConfusionTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(ConfusionTrg, Condition( function Conditions)) call TriggerAddAction(ConfusionTrg, function Actions ) set ConfusionTrg = null //setting globals set g = CreateGroup() set b = Condition(function Targets) //We create our 2D array, so we can set if after set i = 0 loop exitwhen(i == MAX_LEVELS) set playerLevelChances[i] = playerChances.create() set i = i + 1 endloop //We set our 2D array xD call PlayersCallibration() //to prevent lag the first time we use spell call Preload(EFFECT) endfunction endscope Here is map and replay, please help =S EDIT EDIT EDIT I found the problem, the member group array allPlayers[ARRAY_SIZE] is destroyed in the end of the spell. If two spells, with different levels, are cast the following scenario can happen: 1 - 1st cast on unit spell level 1 lasts for 10 seconds 2 - 2nd cast on unit spell level 3 lasts for 30 seconds 3 - level 1 duration ends, array destroyed, we lose original player for ever ... To solve this bug I would need a global array, but then, I wouldn't be able to loop through the units like I do here: JASS://here we walk trhough the group array set i = 0 loop exitwhen(i == ARRAY_SIZE) //here we walk through every single unit inside the group loop set f = FirstOfGroup(data.allPlayers[i]) exitwhen(f == null) call GroupRemoveUnit(data.allPlayers[i], f) call SetUnitOwner(f, Player(i), true) endloop set i = i + 1 endloop I need a solution for this array problem, can some one aid me ? |
| 09-16-2008, 11:25 AM | #2 |
First of all, I'd give every spell instance (the thing you call MyStruct) another group, a master group, that holds all affected units. Then, I'd sort the units in the groups not based on their previous owner, but on their new owner. Then, I'd create one struct at init that would be permanent and would be used to store the original owner of each affected unit. Then, I'd keep a sorted list of all instances based on when they were created, something like this: JASS:globals private MyStruct array instances private integer instanceCount=0 endglobals private struct MyStruct static method create takes unit caster returns MyStruct ... set instances[instanceCount]=data set instanceCount=instanceCount+1 ... endmethod method onDestroy takes nothing returns nothing ... local integer i=0 local boolean b=false set instanceCount=instanceCount-1 loop exitwhen i>=instanceCount if instances[i]==this then set b = true endif if b then set instances[i]=instances[i+1] endif set i=i+1 endloop ... endmethod endstruct With a sorted list like that we can, whenever a spell instance ends, loop backwards through the list to find the newest instance that affects this unit (we check if the unit is in it's master group) and set the unit's owner to whatever player that instance changes it to; if however, there are no more instances affecting this unit, we go back to the original-owner-struct (we could just make that one be the first in the list) and set the unit's owner to the player set there (of course, we had to first store the unit there the first time a spell affected it). A side note: don't create&destroy groups, reuse them: JASS:static method create takes player caster returns MyStruct ... if data.allPlayers[i]==null then //only create new groups if this index hasn't been used before loop exitwhen (i == ARRAY_SIZE) set data.allPlayers[i] = CreateGroup() set i = i + 1 endloop endif ... endmethod method onDestroy takes nothing returns nothing ... loop exitwhen (i == ARRAY_SIZE) call GroupClear(.allPlayers[i]) //save the groups for the next time this index is used set i = i + 1 endloop ... endmethod |
| 09-16-2008, 06:22 PM | #3 | ||
OMG, this is total chinese, I never saw anything like this before ! I will read this better and try to understand it ! Soon new posts ! EDIT EDIT EDIT Quote:
Quote:
Ok, I tried applying your suggestion, but I have problems. I need some concepts to be explained better and your code doesn't seem to compile. (Btw, where the hell do you get such ideas ? This is totally from other world ! I can do system with this stuff ! xD) JASS:globals private SortedList array instances private integer instanceCount = 0 endglobals private struct SortedList static method create takes nothing returns SortedList local SortedList anInstance = SortedList.allocate() set instances[instanceCount] = anInstance set instanceCount = instanceCount + 1 return anInstance endmethod endstruct Compile error: - Undefined type: Sorted List (ya it should be down, but if I do so, I will have an undeclared variable called instances) Can you enlight me better the quoted sections and the code(second example) ? Is GroupClear some system ? Maybe it is a native I don't know, I don't have JNGP now, I will see it later. I assume it clears all units from a group xD Wouldn't it be better to use a group recycling system ? I mean one of those made by Vexorian. What do you advice ? PS: Also, I see you are taking special effort into this, I really thank that and appreciate it. Please don't be mad at me if I don't understand some of your stuff, I am still new at this, I don't have your experience, and mainly, we both know I am no genius xD |
| 09-16-2008, 06:47 PM | #4 | ||||
Quote:
Quote:
Quote:
Quote:
|
| 09-17-2008, 07:09 PM | #5 | |||
Quote:
Quote:
I have lots of questions about the logic you implemented in this, also please answer my other questions when you have time. EDIT EDIT EDIT Quote:
Can you explain me how would this work ?? I don't get where this is going =S EDIT EDIT EDIT Anitarf, since you suggested a path for me to explore, but I need to know what goes exactly in your mind. Can you explain better please ? Also, not I don NOT wan codes, I just want a little text that explains how things would work... if you could help, I would appreciate. |
| 09-17-2008, 08:08 PM | #6 | ||
Quote:
Quote:
|
| 09-17-2008, 08:42 PM | #7 | |
Quote:
I got the list part, you explained it quite well, however I am not seeing this work with the other stuff ! |
| 09-17-2008, 08:54 PM | #8 |
PS: CAn you exemplify your idea with an example ? Or Pseudo code ? PS2: I would really like to make this alone, I just need to know how would the master group and the structure at Init work together ... or the logic behind that ... |
| 09-17-2008, 09:08 PM | #9 | ||||||||||||
Let's say the spell is cast three times. Each time it's cast, it checks for each unit it affects if it's already stored in the "origin" struct, if it isn't yet then it adds the unit there and stores who it's original owner was, if the unit is already there it does nothing. So, let's say we have units A,B,C and D belonging to player 1 affected by these three spell instances: Situation:
Situation:
Situation:
|
| 09-17-2008, 10:07 PM | #10 |
To be continued...:scope confusion initializer init globals private constant integer AID = 'A003' //the rawcode of the ability private constant string EFFECT = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" endglobals private constant function Duration takes integer level returns real return 20. + (level * 10) //the duration of the spell on the units endfunction private constant function Radius takes integer level returns real return 125. + (level * 75) //the radius the spell will have endfunction private function h2i takes handle h returns integer return h return 0 endfunction private struct olddata // a struct to store the old data, uses gamecache to do O(1) searchs static gamecache GC = InitGameCache("Confusion.w3v") player p unit u static method Add takes unit c returns nothing local olddata OD = olddata.allocate() set OD.p = GetOwningPlayer(c) set OD.u = c call StoreInteger(olddata.GC, "olddataindexes", I2S(h2i(c)), integer(OD)) endmethod static method GetOldData takes unit u returns olddata return olddata( GetStoredInteger(olddata.GC, "olddataindexes", I2S(h2i(u))) ) endmethod method onDestroy takes nothing returns nothing call FlushStoredInteger(olddata.GC, "olddataindexes", I2S(h2i(.u))) endmethod endstruct private struct data static group G = CreateGroup() // this group will store ALL the units which are affected by the spell // This group will ensure that units can't be affected twice by different spell casts group g // this one will store the ones that are affected per spell cast private static method Assign takes nothing returns nothing endstruct static method create takes unit c, location l returns data local data D = data.allocate() if D.g == null then set D.g = CreateGroup() endif call GroupEnumUnitsInRangeOfLoc(D.g, l, Radius(GetUnitAbilityLevel(c, AID)), Condition(function data.filter)) call ForGroup(D.g, function data.Assign) endmethod endstruct private function Actions takes nothing returns nothing endfunction private function init takes nothing returns nothing local trigger ConfusionTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(ConfusionTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(ConfusionTrg, Condition( function Conditions)) call TriggerAddAction(ConfusionTrg, function Actions ) set ConfusionTrg = null endfunction endscope |
| 09-18-2008, 11:35 AM | #11 |
OMG I feel so crushed and overwhelmed by all this new complexity. I made no Idea making this spell would be so damn hard ! I am just a begginer and now I am learning all this kind of stuff ! I will try understanding Moyack's code (Thx btw, it will be helpful) because I believe it does what Anitarf has in mind. I never used cache before in all my life, this will be something new.... I just hope I can learn it. I will post questions and codes soon, to prove you guys I am actually trying to understand and learn stuff. If I ever finish this spell, it will be my jewel, but it will not be just mine, it will be from Anitarf and Moyack too. Thx. EDIT EDIT EDIT Ok question: [jass]private struct olddata // a struct to store the old data, uses gamecache to do O(1) searchs static gamecache GC = InitGameCache("Confusion.w3v") player p unit u static method Add takes unit c returns nothing local olddata OD = olddata.allocate() set OD.p = GetOwningPlayer(c) set OD.u = c call StoreInteger(olddata.GC, "olddataindexes", I2S(h2i(c)), integer(OD)) endmethod static method GetOldData takes unit u returns olddata return olddata( GetStoredInteger(olddata.GC, "olddataindexes", I2S(h2i(u))) ) endmethod method onDestroy takes nothing returns nothing call FlushStoredInteger(olddata.GC, "olddataindexes", I2S(h2i(.u))) endmethod endstruct[jass] Ok: 1 - static gamecache GC = InitGameCache("Confusion.w3v") , isn't this evil ? Shouldn't this structure have a create method where I would do this ? 2 - StoreInteger(cache, missionKey, key, integer), what is the difference between missionKey and key ? How will they be used ? 3 - call StoreInteger(olddata.GC, "olddataindexes", I2S(h2i(c)), integer(OD)) Ok so here we add an entire structure (an instance ?) to game cache right ? 4 - call FlushStoredInteger(olddata.GC, "olddataindexes", I2S(h2i(.u))) Here we destroy the instance right ? 5 - This is the Origin structure I have to create on Init correct ? JASS:private struct data static group G = CreateGroup() // this group will store ALL the units which are affected by the spell // This group will ensure that units can't be affected twice by different spell casts group g // this one will store the ones that are affected per spell cast private static method Assign takes nothing returns nothing endstruct static method create takes unit c, location l returns data local data D = data.allocate() if D.g == null then set D.g = CreateGroup() endif call GroupEnumUnitsInRangeOfLoc(D.g, l, Radius(GetUnitAbilityLevel(c, AID)), Condition(function data.filter)) call ForGroup(D.g, function data.Assign) endmethod endstruct Ok: 1 - This structure is the MyStruct structure correct ? 2 - JASS:private static method Assign takes nothing returns nothing endstruct What is group "g" for ? 3 - JASS:static method create takes unit c, location l returns data local data D = data.allocate() if D.g == null then set D.g = CreateGroup() endif call GroupEnumUnitsInRangeOfLoc(D.g, l, Radius(GetUnitAbilityLevel(c, AID)), Condition(function data.filter)) call ForGroup(D.g, function data.Assign) endmethod Also, according to this line: call GroupEnumUnitsInRangeOfLoc(D.g, l, Radius(GetUnitAbilityLevel(c, AID)), Condition(function data.filter)) I should add a "filter" method correct ? Finally call ForGroup(D.g, function data.Assign), I made no idea we could use methods in ForGroupBJ... Ok, guys if this is what Anitarf had in mind, I am sorry to disappoint him, I would've never reach this code alone =S I just wonder if this is "too much sand for my truck" but I must try ! Thx for posting code. Can some now answer ? PS: I know this seems a JASS class, sorry for that =S |
| 09-19-2008, 01:53 AM | #12 |
Before explaining anything, I'll talk about how I imagine this spell should work, I hope not to confuse and if it happens please let me know. I'll resume in points my imaginary about what this spell should do:
Now the things to take into consideration when this spell should be developed:
Now the technical information. What data should be saved about the affected units?
With this very clarified, we can now proceed with the spell development. following the spell steps we can relate what jass stuff we will require. I have to confess I'm a struct whore and for that reason my code tends to be "encapsulated" in structs. Why I do this? because structs help you to keep your code very organized, and they can take different roles in a code without changing them too much. Ok, let's continue with the explanation about this spell. We need that this spell groups the units affected by the spell and then change their owning player to other players according a set of conditions and probabilities. First, let's do a function that takes the group of units and store in each of them their original owning player. JASS:private struct Spell // this struct will manage the spell process group g // this group component will store all the units affected by the spell endstruct Because this spell is AOE and we need to catch units in a range, then we must base this spell on an ability that is normally used to affect cluster of units. My options goes to Blizzard or Rain of Fire. So this spells allow us to get the spell location and manages AOE, then... let's use any of them. Ok, now we'll add to the struct a method to catch the units in the range of effect and process them properly. JASS:private struct Spell // this struct will manage the spell process group g // this group component will store all the units affected by the spell static method Add takes location l returns Spell local Spell S = Spell.allocate() if S.g == null then set S.g = CreateGroup() endif call GroupEnumUnitsInRangeOfLoc( // to be continued... endmethod endstruct To be continued... (I need to sleep) |
| 09-19-2008, 02:37 PM | #13 | ||||
Quote:
About the units affected, it really doesn't matter IMO, since if we don't like them, we just change the boolean function nice and easy. And yes, my main problem is making this spell stackable and MUI ... sounds like impossible mission for me ... Quote:
Quote:
I seriously believe you are complicating. I don't have a method to add units, and it is still very easy to understand. Anyway, that is not important, this important thing is that the units in affected by this spells instance get to group "g", and I can do that without methods (lol). I now how to do some of the stuff, however if you start talking about cache and all that stuff, I really get lost. I find it most obvious I have to remake the spell now and that I won't be able to send it to the spell olympics, but it doesn't matter. I fel it is obvious I made some mistake in the planning stage and now the price for that mistake is starting again ... damn. Quote:
It is still day where I live lol =P ... |
| 09-22-2008, 02:15 PM | #14 | |||||||||
Sorry, I've been busy, so I decided to answer your questions directly... Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
|
| 09-24-2008, 09:21 PM | #15 |
Well, as we say in my country, better late than never. I just wish I could've delivered this spell into the Olympics. Anyway, most this concepts you present here to me are new, some either confusing. I really start doubting my capabilities to build such a spell. I will review this later once again. It late in my country, and now, I must also rest my mind. Thx for help though =) |
