| 04-24-2008, 11:05 PM | #1 | ||
FULMINATION
by HINDYhat (complies with the JESP standard) Description:
Credits/Thanks to:
REMEMBER to adjust mana cost and cooldown to your needs. I left them null here for testing reasons! IMPORTANT : This spell requires JassNewGenPack for vJass syntax! Click here to download JassNewGenPack : http://www.wc3campaigns.net/showthread.php?t=90999 Spell code:scope Fulmination globals private constant integer ABILITY_RAWCODE = 'A000' // The rawcode of the casted Fulmination ability. private constant integer DUMMY_RAWCODE = 'e000' // The "Water Particle"'s rawcode. private constant real FRAMERATE = 40.0 // The framerate (frames per second) of the main function. private constant real GRAVITY_ACCEL = -600.0 // Gravity's acceleration in wc3units per second per second. private constant integer PARTICLE_AMOUNT = 12 // The amount of particles created per cast. private constant real INITIAL_VELOCITY = 250.0 // The initial velocity of each particle. private constant real INCR_VELOCITY = 150.0 // The increment in velocity of each particle based on hero level. private constant real INITIAL_DURATION = 3.5 // The initial duration of the spell. private constant real INCR_DURATION = 0.5 // The increment in duration of the spell based on hero level. private constant real FRICTION_COEFF = 0.95 // The coefficient in velocity par particles during ground collision. private constant real RESTITUTE_DURATION = 2.0 // The time required for all particles to reassemble. private constant real INITIAL_HEIGHT = 50.0 // The initial created height offset for particles. private constant real PT_TARGET_SPEED = 500.0 // The speed at which particles are propulsed on point target orders. private constant real AOA_MINIMUM = bj_PI/6.0 // The minimum angle of attack towards which particles are projected on cast. private constant real AOA_MAXIMUM = bj_PI/2.5 // The maximum angle of attack towards which particles are projected on cast. private constant real COLLISION_RADIUS = 4.0 // The collision radius for particles and the ground. private constant string PARTICLE_FX = "Abilities\\Weapons\\SeaElementalMissile\\SeaElementalMissile.mdl" // The particle model effect. private constant string ONCAST_FX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The model effect displayed on the caster on cast. private constant string ONRESTITUTE_FX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // The model effect displayed on the caster on reassembly of particles. endglobals //-----------------------------------------------------// // ~~~~~~~~~~~~~~~~ DO NOT EDIT BELOW ~~~~~~~~~~~~~~~~ // //-----------------------------------------------------// private function Conditions takes nothing returns boolean return GetSpellAbilityId() == ABILITY_RAWCODE endfunction globals private location tloc private location tloc2 private real minX private real minY private real maxX private real maxY private timer T endglobals private keyword main private struct particleGroup unit caster effect array e[PARTICLE_AMOUNT] unit array dummy[PARTICLE_AMOUNT] real array xP[PARTICLE_AMOUNT] real array yP[PARTICLE_AMOUNT] real array zP[PARTICLE_AMOUNT] real array xV[PARTICLE_AMOUNT] real array yV[PARTICLE_AMOUNT] real array zV[PARTICLE_AMOUNT] trigger pointTarget real duration integer ID static particleGroup array P static integer COUNT = 0 static particleGroup array P_D static integer COUNT_D = 0 method onDestroy takes nothing returns nothing local integer i = 0 loop exitwhen i >= PARTICLE_AMOUNT call DestroyEffect(this.e[i]) set this.xV[i] = 0.0 set this.yV[i] = 0.0 call KillUnit(this.dummy[i]) call ShowUnit(this.dummy[i], false) set i = i + 1 endloop call DestroyTrigger(this.pointTarget) set particleGroup.COUNT = particleGroup.COUNT - 1 set particleGroup.P[this.ID] = particleGroup.P[particleGroup.COUNT] set particleGroup.P[this.ID].ID = this.ID if particleGroup.COUNT == 0 then call PauseTimer(T) endif endmethod static method pointOrder takes nothing returns boolean local unit u = GetTriggerUnit() local particleGroup p = particleGroup(GetUnitUserData(u)) local integer i = 0 local real x = GetOrderPointX() local real y = GetOrderPointY() if p.duration > 0.0 then loop exitwhen i >= PARTICLE_AMOUNT if p.dummy[i] == u then call MoveLocation(tloc, x, y) set p.xV[i] = x - p.xP[i] set p.yV[i] = y - p.yP[i] set p.zV[i] = GetLocationZ(tloc) - p.zP[i] set y = PT_TARGET_SPEED/(SquareRoot(p.xV[i]*p.xV[i] + p.yV[i]*p.yV[i] + p.zV[i]*p.zV[i])*FRAMERATE + 0.01) set p.xV[i] = p.xV[i]*y set p.yV[i] = p.yV[i]*y set p.zV[i] = p.zV[i]*y return false endif set i = i + 1 endloop endif set u = null return false endmethod static method onInit takes nothing returns nothing set tloc = Location(0.0, 0.0) set tloc2 = Location(0.0, 0.0) set minX = GetRectMinX(bj_mapInitialPlayableArea) set minY = GetRectMinY(bj_mapInitialPlayableArea) set maxX = GetRectMaxX(bj_mapInitialPlayableArea) set maxY = GetRectMaxY(bj_mapInitialPlayableArea) set T = CreateTimer() endmethod endstruct private function main takes nothing returns nothing local integer i = 0 local integer k = 0 local particleGroup p local real x local real y local real z loop exitwhen i >= particleGroup.COUNT set p = particleGroup.P[i] if p.duration <= -1.0 then loop exitwhen k >= PARTICLE_AMOUNT set p.xP[k] = p.xP[k] + p.xV[k] set p.yP[k] = p.yP[k] + p.yV[k] set p.zP[k] = p.zP[k] + p.zV[k] call SetUnitX(p.dummy[k], p.xP[k]) call SetUnitY(p.dummy[k], p.yP[k]) call MoveLocation(tloc, p.xP[k], p.yP[k]) set z = GetLocationZ(tloc) if p.zP[k] > z then call SetUnitFlyHeight(p.dummy[k], p.zP[k] - z, 0.0) endif set k = k + 1 endloop set p.duration = p.duration - 1.0/FRAMERATE if p.duration <= -1.0 - RESTITUTE_DURATION then call SetUnitPosition(p.caster, p.xP[0], p.yP[0]) call ShowUnit(p.caster, true) if GetLocalPlayer() == GetOwningPlayer(p.caster) then call ClearSelection() call SelectUnit(p.caster, true) endif call DestroyEffect(AddSpecialEffectTarget(ONRESTITUTE_FX, p.caster, "origin")) set particleGroup.P_D[particleGroup.COUNT_D] = p set particleGroup.COUNT_D = particleGroup.COUNT_D + 1 endif else loop exitwhen k >= PARTICLE_AMOUNT set p.zV[k] = p.zV[k] + GRAVITY_ACCEL/(FRAMERATE*FRAMERATE) set p.xP[k] = p.xP[k] + p.xV[k] set p.yP[k] = p.yP[k] + p.yV[k] set p.zP[k] = p.zP[k] + p.zV[k] if p.xP[k] < minX or p.yP[k] < minY or p.xP[k] > maxX or p.yP[k] > maxY then set p.xP[k] = p.xP[k] - p.xV[k] set p.yP[k] = p.yP[k] - p.yV[k] set p.xV[k] = 0.0 set p.yV[k] = 0.0 endif call MoveLocation(tloc, p.xP[k], p.yP[k]) set z = GetLocationZ(tloc) call SetUnitX(p.dummy[k], p.xP[k]) call SetUnitY(p.dummy[k], p.yP[k]) call SetUnitFlyHeight(p.dummy[k], p.zP[k] - z, 0.0) if z > p.zP[k] then set p.zP[k] = z call MoveLocation(tloc, p.xP[k] - COLLISION_RADIUS, p.yP[k]) call MoveLocation(tloc2, p.xP[k] + COLLISION_RADIUS, p.yP[k]) set x = GetLocationZ(tloc) - GetLocationZ(tloc2) call MoveLocation(tloc, p.xP[k], p.yP[k] - COLLISION_RADIUS) call MoveLocation(tloc2, p.xP[k], p.yP[k] + COLLISION_RADIUS) set y = GetLocationZ(tloc) - GetLocationZ(tloc2) set z = -2.0*(p.xV[k]*x + p.yV[k]*y + p.zV[k]*2.0*COLLISION_RADIUS)/(x*x + y*y + 4.0*COLLISION_RADIUS*COLLISION_RADIUS) set p.xV[k] = FRICTION_COEFF*(p.xV[k] + x*z) set p.yV[k] = FRICTION_COEFF*(p.yV[k] + y*z) set p.zV[k] = FRICTION_COEFF*(p.zV[k] + 2.0*COLLISION_RADIUS*z) endif set k = k + 1 endloop set p.duration = p.duration - 1.0/FRAMERATE if p.duration <= 0.0 then set x = p.xP[0] set y = p.yP[0] set k = 1 loop exitwhen k >= PARTICLE_AMOUNT set x = x + p.xP[k] set y = y + p.yP[k] call IssueImmediateOrder(p.dummy[k], "stop") call UnitAddAbility(p.dummy[k], 'Aloc') set k = k + 1 endloop set x = x/PARTICLE_AMOUNT set y = y/PARTICLE_AMOUNT call MoveLocation(tloc, x, y) set z = GetLocationZ(tloc) set k = 0 loop exitwhen k >= PARTICLE_AMOUNT set p.xV[k] = (x - p.xP[k])/(RESTITUTE_DURATION*FRAMERATE) set p.yV[k] = (y - p.yP[k])/(RESTITUTE_DURATION*FRAMERATE) set p.zV[k] = (z + INITIAL_HEIGHT - p.zP[k])/(RESTITUTE_DURATION*FRAMERATE) set k = k + 1 endloop set p.duration = -1.0 endif endif set k = 0 set i = i + 1 endloop set i = 0 loop exitwhen i >= particleGroup.COUNT_D call particleGroup.P_D[i].destroy() set i = i + 1 endloop set particleGroup.COUNT_D = 0 endfunction private function Actions takes nothing returns nothing local particleGroup p = particleGroup.create() local integer i = 0 local unit caster = GetTriggerUnit() local player owner = GetOwningPlayer(caster) local integer level = GetUnitAbilityLevel(caster, ABILITY_RAWCODE) local real speed = (INITIAL_VELOCITY + level*INCR_VELOCITY)/FRAMERATE local real yaw local real aoa local real cos local real x = GetUnitX(caster) local real y = GetUnitY(caster) local real z call MoveLocation(tloc, x, y) set z = GetLocationZ(tloc) set p.pointTarget = CreateTrigger() call SelectUnit(caster, GetLocalPlayer() != owner) loop exitwhen i >= PARTICLE_AMOUNT set yaw = i*360.0/PARTICLE_AMOUNT set p.dummy[i] = CreateUnit(owner, DUMMY_RAWCODE, x, y, yaw) set p.xP[i] = x set p.yP[i] = y call UnitAddAbility(p.dummy[i], 'Amrf') call UnitRemoveAbility(p.dummy[i], 'Amrf') call UnitAddAbility(p.dummy[i], 'Aloc') call ShowUnit(p.dummy[i], false) call UnitRemoveAbility(p.dummy[i], 'Aloc') call ShowUnit(p.dummy[i], true) call SelectUnit(p.dummy[i], GetLocalPlayer() == owner) call SetUnitFlyHeight(p.dummy[i], INITIAL_HEIGHT + GetUnitFlyHeight(caster), 0.0) set p.e[i] = AddSpecialEffectTarget(PARTICLE_FX, p.dummy[i], "origin") set p.zP[i] = z + GetUnitFlyHeight(p.dummy[i]) set aoa = GetRandomReal(AOA_MINIMUM, AOA_MAXIMUM) set cos = Cos(aoa)*speed set yaw = yaw*bj_DEGTORAD set p.xV[i] = cos*Cos(yaw) set p.yV[i] = cos*Sin(yaw) set p.zV[i] = Sin(aoa)*speed call TriggerRegisterUnitEvent(p.pointTarget, p.dummy[i], EVENT_UNIT_ISSUED_POINT_ORDER) call SetUnitUserData(p.dummy[i], integer(p)) set i = i + 1 endloop call TriggerAddCondition(p.pointTarget, Filter(function particleGroup.pointOrder)) set p.caster = caster call ShowUnit(caster, false) call DestroyEffect(AddSpecialEffect(ONCAST_FX, x, y)) set p.duration = INITIAL_DURATION + level*INCR_DURATION set p.ID = particleGroup.COUNT set particleGroup.P[particleGroup.COUNT] = p set particleGroup.COUNT = particleGroup.COUNT + 1 if particleGroup.COUNT == 1 then call TimerStart(T, 1.0/FRAMERATE, true, function main) endif endfunction public function InitTrig takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(trig, Condition(function Conditions)) call TriggerAddAction(trig, function Actions) call Preload(PARTICLE_FX) call Preload(ONCAST_FX) call Preload(ONRESTITUTE_FX) endfunction endscope |
| 04-25-2008, 06:54 PM | #3 |
Spherical coordinates in a WC3 spell, man, this is going to be a blast to go through this weekend. |
| 04-25-2008, 11:21 PM | #4 |
I hope that was a good thing xD |
| 05-01-2008, 04:17 PM | #5 |
JASS:struct particleGroup Oh, and does this comply with the JESP Manifest? It looks like it does, all you'd need is to put the JESP manifest into your map in a disabled trigger. I think all spells should comply with JESP if they can since it's a great and maleable standard for spells. |
| 05-01-2008, 05:43 PM | #6 |
Yes it does comply with the standards. I'll update it now. About the private struct, I simply forgot xD. I also think it's MUI, but I haven't tested it to death. EDIT: Hmmm, making the struct private forbids me from referring to a particleGroup type object inside function main. I placed function main below the struct, and made a keyword for it. Works now. Also, tested the MUI part, and it works fine. |
| 05-02-2008, 02:44 PM | #7 |
Works for MUI, runs clean, JESP manifest in testmap, read me and implementation instructions check... I dare say approved, good sir. |
| 05-02-2008, 06:08 PM | #8 |
That was quick! :D Thanks for the approval. |
| 05-02-2008, 07:50 PM | #9 |
Woah such a nice spell. This is pretty awesome And so many units are moved without any lag, and they bounce and stuff. Really, really nice. How about adding a damage function at the point where the unit is recreated? |
| 05-02-2008, 08:24 PM | #10 | |
Quote:
|
| 05-03-2008, 09:32 AM | #11 |
Cool spell concept. I have a few minor suggestions. 1.) When the water bits reform, you should automatically reselect the elemental. 2.) Location() and CreateTimer() can be used inside a global block so if you want they can be removed from onInit. 3.) There is no point to using an animation timer faster than 40 updates per second. Seriously, try it, I recommend defaulting to 40 updates per sec. |
| 05-03-2008, 10:51 AM | #12 |
1) Possibly. 2) They can be used in a global block indeed, but someone told me it's bad to initialize globals in that way (forgot who, maybe I misunderstood too). 3) Easily configurable. |
| 05-03-2008, 11:23 AM | #13 |
Really nice idea for a spell. A couple of things:
About 2; Global init has an op-limit (which includes all the globals in the common.j and blizzard.j). By initializing them elsewhere you avoid the potential for this to cause problems. |
| 05-03-2008, 10:55 PM | #14 | |
Quote:
Well unless you do some incredible number of things on init you will get nowhere close to the op limit so it seems senseless to do extra typing for that. |
| 05-04-2008, 12:08 PM | #15 |
Holding down Alt I could see 2mm x 2mm hit point bars, which looks bad. Bake them full or remove them! Anyway, nice spell. |
