//TESH.scrollpos=53
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
Name | Type | is_array | initial_value |
GameCache | gamecache | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
This is the JESP standard document, if a map contains this document it means that there are
spells that follow this standard.
Spells of this map that follow the standard:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- Sacred Circle 2
(must change this part to mention the spells)
Advantages of the Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- Implementing spells that follow the standard is relatively easier than implementing JASS
spells that don't follow the standard.
- Configuring/Balancing spells that follow the standard is relatively easier than
implementing JASS spells that don't follow the standard.
- Users may do the following procedure to make a new ability that uses the spell's script :
* Create a new Trigger with a name (case sensitive)
* Convert that trigger to custom text.
* Copy the spell's script to a text editor like Notepad or your OS equivalent.
* Replace the spell's Code name with the name you used on the trigger.
* Copy the new text to the new trigger
* Duplicate the Spell's original objects to have new ones for the new spell script.
You are now able to use that new version of the spell.
- In case two guys give the same name to 2 different spells, there are no conflict problems
because you can easily change the name of one of them
What is the JESP Standard?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The JESP standard was designed to make spell sharing much better. And to make sure JASS
enhanced spells follow a rule, to prevent chaos.
What does JESP Standard stands for?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
JASS
Enhanced
Spell
Pseudotemplate
Requirements for a spell to follow the JESP Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- The spell is written in JASS
- The spell is 100% multi instanceable.
- The spell script is ready to support spells of any number of levels.
(default config header is not required to support all of them)
- The Spell has an specific code name.
- The Spell's trigger must have the spell's codename as name
- The Spell's InitTrig function must be named: InitTrig_<CodeName>
- The spell has a configuration header.
- It is mandatory that rawcodes of objects are configurable in the header.
- All the spell's specific code is inside the spell's "Trigger" (Trigger== that custom text
slot that world editor calls Trigger, the spell may use as many 'trigger' OBJECTS as needed)
- Every spell-specific single identifier or key works in such a way that reproducing the
spell's trigger but after performing a text-replace of codename with another name (and thus
renaming the cloned trigger to the new code name) it won't cause compile errors / conflicts
when playing the map.
- There is no code inside the spell's "Trigger" that is not specific to the spell.
- There are no requirements for GUI variables that are specific to the spell. If a system
used by the spell requires GUI variables the code for the system must be outside the "Trigger"
- Eyecandy and spell's balance have to be easy to configure
- The name of the author should be included in the spell's script.
- The reason to exist of this standard is spell sharing. This document should be included
within the map. And it should specify which spell follows the standard, in the top list.
//TESH.scrollpos=124
//TESH.alwaysfold=0
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
//¤
//¤ *****************
//¤ - Sacred Circle -
//¤ *****************
//¤
//¤ By: Daxtreme
//¤
//¤ --> How to implement in your map:
//¤
//¤ 1. Copy the spell "Sacred Circle" in your map.
//¤ 2. Copy everything found in the "Custom script code" section. To do this, click
//¤ on the name of the map in the top-left corner in the trigger editor.
//¤ 3. Copy this trigger into your map.
//¤ 4. Import the HolyStrike.mdx model in your map.
//¤
//¤ --> How to customize it:
//¤
//¤ You can configure the spell using the constant functions just below. Change their values as needed.
//¤
//¤ CREDITS:
//¤
//¤ - JetFangInferno's Holy Strike.
//¤ - kenny! for testing, bug-finding, and updating!
//¤ - Rising_Dusk for the GroupUtils system.
//¤
//¤
//¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
scope SacredCircle2
globals
private constant integer ABIL_ID = 'A000' // Sacred Circle's object editor ability Id
private constant integer ORDER_ID = 852183 // DO NOT TOUCH.
private constant real INTERVAL = 0.04 // Period. An holy bolt is created every (period) seconds.
// The total time the spell lasts is defined as follow:
// TIME * INTERVAL
private constant real TIME = 100 // This is the factor multiplying the variable "INTERVAL". The total
// time the spell lasts is defined as follow:
// TIME * INTERVAL
private constant real DAMAGE = 100. // This value x level = total damage per holy bolt.
private constant real BASERADIUS = 175. // Base radius of the Holy Bolts without considering the level.
private constant real LVLRADIUS = 0. // Damage radius increment. It is multiplied by the level of the spell.
private constant real DIS_CONSTANT = 50. // Base distance between each expanding Holy Bolt
private constant real DIS_FACTOR = 10. // Factor multiplying the distance increment between each expanding
// Holy Bolt. Formula is: D_FACTOR * D_INCREMENT
// Increasing this value will increase the distance between each
// Holy Bolts.
private constant real DIS_INCREMENT= 1.00 // Difference in distance between the Holy Bolts and the caster.
// Increasing this value will increase the speed at which the circle
// expands.
private constant real NB_BOLTS = 15. // Number of bolts needed to make a full clockwise rotation
// around the caster. Keep in mind that the longer the spell lasts,
// the more distance there will naturally be between the Holy Bolts.
private constant string EFFECT = "war3mapImported\\HolyStrike.mdx" // Spell model art
private constant attacktype A_TYPE = ATTACK_TYPE_CHAOS
private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype W_TYPE = WEAPON_TYPE_WHOKNOWS
private constant boolean STOP_FIRST = false
endglobals
// *******************************************************************
// END OF CONFIGURATION SECTION
// ====================================================================================================================
private function Damage_dealt takes integer lvl returns real
return DAMAGE * lvl
endfunction
private function Damage_radius takes integer lvl returns real
return BASERADIUS + (LVLRADIUS * lvl)
endfunction
private function Filter_enemies takes unit filter, unit caster returns boolean
return GetWidgetLife(filter) > 0.406 and IsUnitEnemy(filter,GetOwningPlayer(caster)) == true and IsUnitType(filter,UNIT_TYPE_MAGIC_IMMUNE) == false
endfunction
private struct Data
unit cast = null
real time = 0.00
real dist = 0.00
real ang = 0.00
integer lvl = 0
boolean stop = false
real x = 0.00
real y = 0.00
static Data array D
static integer D_total = 0
static timer Timer = null
static boolexpr Filt = null
static method filt takes nothing returns boolean
return true
endmethod
method search takes nothing returns nothing
local integer i = 1
loop
exitwhen i > Data.D_total
if Data.D[i].cast == .cast then
if STOP_FIRST then
set Data.D[i].stop = true
else
set .stop = true
endif
endif
set i = i + 1
endloop
endmethod
method periodic takes nothing returns boolean
local unit u
local real x
local real y
if GetUnitCurrentOrder(.cast) != ORDER_ID or .time <= 0 then
return true
else
set x = .x + (DIS_CONSTANT + .dist * DIS_FACTOR) * Cos(.ang)
set y = .y + (DIS_CONSTANT + .dist * DIS_FACTOR) * Sin(.ang)
call DestroyEffect(AddSpecialEffect(EFFECT,x,y))
call GroupEnumUnitsInArea(ENUM_GROUP,x,y,Damage_radius(.lvl),Data.Filt)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP,u)
if Filter_enemies(u,.cast) then
call UnitDamageTarget(.cast,u,Damage_dealt(.lvl),false,false,A_TYPE,D_TYPE,W_TYPE)
endif
endloop
set .ang = .ang + 6.283185 / NB_BOLTS - .dist *0.002909
set .dist = (.dist + DIS_INCREMENT)
set .time = (.time - INTERVAL)
endif
return false
endmethod
static method update takes nothing returns nothing
local integer i = 1
loop
exitwhen i > Data.D_total
if Data.D[i].stop or Data.D[i].periodic() then
call Data.D[i].destroy()
set Data.D[i] = Data.D[Data.D_total]
set Data.D_total = Data.D_total - 1
set i = i - 1
endif
set i = i + 1
endloop
if Data.D_total <= 0 then
call PauseTimer(Data.Timer)
set Data.D_total = 0
endif
endmethod
static method actions takes nothing returns boolean
local Data d = Data.create()
set d.cast = GetTriggerUnit()
set d.lvl = GetUnitAbilityLevel(d.cast,ABIL_ID)
set d.time = TIME * INTERVAL
set d.x = GetUnitX(d.cast)
set d.y = GetUnitY(d.cast)
call d.search()
set Data.D_total = Data.D_total + 1
set Data.D[Data.D_total] = d
if Data.D_total == 1 then
call TimerStart(Data.Timer,INTERVAL,true,function Data.update)
endif
return false
endmethod
static method conditions takes nothing returns boolean
return GetSpellAbilityId() == ABIL_ID
endmethod
static method onInit takes nothing returns nothing
local trigger trig = CreateTrigger()
local integer i = 0
call Preload(EFFECT)
set Data.Timer = CreateTimer()
set Data.Filt = Filter(function Data.filt)
loop
call TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,Data.Filt)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(trig,Condition(function Data.conditions))
call TriggerAddAction(trig,function Data.actions)
endmethod
endstruct
endscope