Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//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:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- "Stupid Penguin"
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=0
//TESH.alwaysfold=0
//===========================================================================
//A spell that creates a penguin which will grow every second, and then, when
//it becomes to big, it BOOMMM !! explodes xD
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix
//
//@credits
//- Anitarf, chobibo, Pyrogasm and Captain Griffen
//- My first teacher of vJASS: Blue_Jeans
//- All other people I forgot or ignored
//
//@version 2.0
//===========================================================================
scope StupidPenguin initializer Init
//don't change this global
globals
private unit tmpCaster = null
endglobals
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer AID = 'A000' //The rawcode of the Stupid Penguin ability
private constant string EXPLOSION_EFFECT = "Objects\\Spawnmodels\\NightElf\\NEDeathMedium\\NEDeath.mdl" //explosion effect when penguin dies
private constant string DEATH_EFFECT = "Objects\\Spawnmodels\\Orc\\OrcSmallDeathExplode\\OrcSmallDeathExplode.mdl" //death's effect of penguin
private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" //effect shown when units take damage
private constant string BIRTH_EFFECT = "Objects\\Spawnmodels\\Other\\ToonBoom\\ToonBoom.mdl" //effect shown when pinguin is created
private constant integer TARGET_RED = 0 //the red RGB color for the text tag that will appear above the targets when they are hit by the explosion
private constant integer TARGET_GREEN = 0 //the green RGB color for the text tag that will appear above the targets when they are hit by the explosion
private constant integer TARGET_BLUE = 255 //the blue RGB color for the text tag that will appear above the targets when they are hit by the explosion
private constant integer CASTER_RED = 255 //the red RGB color for the text tag that will appear above the caster, every cycle of channel time
private constant integer CASTER_GREEN = 0 //the green RGB color for the text tag that will appear above the caster, every cycle of channel time
private constant integer CASTER_BLUE = 0 //the blue RGB color for the text tag that will appear above the caster, every cycle of channel time
endglobals
private constant function penguinID takes integer level returns integer
//this if statement is just to show you, how you can create different units
//depending on level. I want to always have a penguin, so I really didn't need
//this statement, having just " return 'npng' " would be Ok
if (level > 0) then
return 'npng'
endif
return 0
endfunction
private constant function growTime takes integer level returns real
return 1. + (level * 0) //time intervail between each time the unit grows (is a cycle)
endfunction
private constant function colorTime takes integer level returns real
return 0.5 + (level * 0) //this is the amount of time a color will remain, before changing
endfunction
private function colorRed takes integer level returns integer
//this function tells us the red gradient RGB color the penguin will have when it's time
//to change color. Here i want a random red gradient, but you can use level like this:
//return color * level, where "color" is the INTEGER number you want
//NOTE: the red gradient goes from 0 to 255 only !
return GetRandomInt(0, 255)
endfunction
private function colorGreen takes integer level returns integer
//this function tells us the green RGB color the penguin will have when it's time
//to change color. Here i want a random green gradient, but you can use level like this:
//return color * level, where "color" is the INTEGER number you want
//NOTE: the green gradient goes from 0 to 255 only !
return GetRandomInt(0, 255)
endfunction
private function colorBlue takes integer level returns integer
//this function tells us the blue RGB color the penguin will have when it's time
//to change color. Here i want a random blue gradient, but you can use level like this:
//return color. * level, where "color" is the INTEGER number you want
//NOTE: the blue gradient goes from 0 to 255 only !
return GetRandomInt(0, 255)
endfunction
private function colorAlpha takes integer level returns integer
//this function tells us the Alpha the penguin will have when it's time
//to change color. Alpha value is responsable for the transparency of the unit.
//Here i want a constant alpha, so the penguin is always visible
//at it's best, but you can use level like this:
//return alpha * level, where "alpha" is the INTEGER number you want
return 255
endfunction
private constant function growth takes integer level returns real
return 0.1 * level //How much the unit will grow. It grows 10% * level in this case
endfunction
private constant function radius takes integer level returns integer
return 200 + (50 * (level - 1)) //the area of damage
endfunction
private constant function damage takes integer level returns real
return 10. * level //Damage increase per cycle
endfunction
private function casterText takes integer damageAdd returns string
//the text that will appear above the caster every cycle
return I2S(damageAdd) + " damage!"
endfunction
private function unitText takes integer unitDamage returns string
//the text that will appear above the unit when the penguin explodes
return "-" + I2S(unitDamage) + " !"
endfunction
private function targets takes nothing returns boolean
//these are the targets the explosion will affect !
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(tmpCaster)) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(GetFilterUnit()) > 0.405)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group StupidPenguinCasters //to store the casters
private group StupidPenguins //to store the penguins
private HandleTable activeTable //your private Table's global variable
private group g //the group that will store the units to suffer the effect of the explosion
private boolexpr b //the units that will be selected into group g
endglobals
private struct MyStruct
unit caster
integer level
unit penguin
real currentScale
timer growPeriod
timer colorTicker
real grow
real damage
boolean hasCasterStoped //this tells us if the spell is ending or not
static method create takes unit caster, real spellX, real spellY returns MyStruct
local MyStruct data = MyStruct.allocate()
//set variables about the caster
set data.caster = caster
set data.level = GetUnitAbilityLevel(data.caster, AID)
//while false, the caster is channeling,
//when true the caster stoped and we stop the spell too
set data.hasCasterStoped = false
//set variables about the Stupid Penguin
set data.penguin = CreateUnit(GetOwningPlayer(data.caster), penguinID(data.level), spellX, spellY, 0.)
set data.currentScale = 1
set data.growPeriod = NewTimer()
set data.colorTicker = NewTimer()
set data.grow = growth(data.level)
set data.damage = damage(data.level)
//put the struct in the Table, we just use the caster's and the
//penguin's handle adress as the key which tells us where in the
//Table the struct is stored
set activeTable[data.caster] = data
set activeTable[data.penguin] = data
//we add the casting unit and the penguin to some sort of "pool"
//with all other casters and penguins that are using this spell
call GroupAddUnit(StupidPenguinCasters, data.caster)
call GroupAddUnit(StupidPenguins, data.penguin)
return data
endmethod
method onDestroy takes nothing returns nothing
//since the spell is not active anymore, we clean the Table
call activeTable.flush(.caster)
call activeTable.flush(.penguin)
//the units are not anymore in the active units group.
call GroupRemoveUnit(StupidPenguinCasters, .caster)
call GroupRemoveUnit(StupidPenguins, .penguin)
//releasing the timer for CSSafety to use it one day later =D
call ReleaseTimer(.growPeriod)
call ReleaseTimer(.colorTicker)
endmethod
endstruct
//===========================================================================
private function explosion takes integer structure returns nothing
//variables for the explosion!
local MyStruct data = structure
local unit f
local texttag text
//blow the penguin to hell !! xD
call DestroyEffect(AddSpecialEffect(EXPLOSION_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin)))
call DestroyEffect(AddSpecialEffect(DEATH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin)))
call ShowUnit(data.penguin, false)//hide the penguin
//if the penguin is not dead, we kill him MUHAHHAHAHHA
if (GetWidgetLife(data.penguin) > 0.405) then
call KillUnit(data.penguin)
endif
set tmpCaster = data.caster
call GroupEnumUnitsInRange(g, GetUnitX(data.penguin), GetUnitY(data.penguin), radius(data.level), b)
loop
set f = FirstOfGroup(g)
exitwhen (f == null)
call GroupRemoveUnit(g, f)
// the effect when the unit is damaged and the damage
call DestroyEffect(AddSpecialEffect(BLOOD_EFFECT, GetUnitX(f), GetUnitY(f)))
call UnitDamageTarget(data.penguin, f, data.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
//now we show some text
set text = CreateTextTag()
call SetTextTagText(text, unitText(R2I(data.damage)), .023 )
call SetTextTagPosUnit( text, f, 0 )
call SetTextTagColor( text, TARGET_RED, TARGET_GREEN, TARGET_BLUE, 255 )
call SetTextTagPermanent(text, false)
call SetTextTagVelocity( text, 0, .0277 )
call SetTextTagLifespan(text, 2.0)
endloop
set text = null
endfunction
//===========================================================================
private function onStop takes nothing returns boolean
local MyStruct data
local unit u = GetTriggerUnit()
//this will save you unnecessary gamecache calls during units' deaths.
//It also prevents conflicts with units getting the same handle id as a ghost,
//however, since you flush when the spell ends that's most likely not an issue.
if(IsUnitInGroup(u, StupidPenguinCasters)) then
//recover the data from the caster
set data = activeTable[u]
set data.hasCasterStoped = true
//here we make things blow up ! An explosion easy to customize
call explosion(data)
//now, time to clean the mess once again
call data.destroy()
endif
set u = null
return false
endfunction
//===========================================================================
private function onDeath takes nothing returns boolean
local MyStruct data
local unit u = GetTriggerUnit()
//we make sure that the unit that dies is the penguin
if(IsUnitInGroup(u, StupidPenguins)) then
//recover that data (the struct) from the penguin
set data = activeTable[u]
//we make a check: if the caster already stoped, the penguin has just
//been killed in the "explosion" function and we don't need to make him
//stop, in the case the variable "hasCasterStoped" is equal to true and
//we just jump this if, thus doing nothing.
//However, if the penguin died first, the "hasCasterStoped" is equal
//to false, and so we order the caster to stop, thus ending the spell.
if not(data.hasCasterStoped) then
call IssueImmediateOrder(data.caster, "stop" )
endif
endif
set u = null
return false
endfunction
//===========================================================================
private function ChangeColor takes nothing returns nothing
local MyStruct data = MyStruct(GetTimerData(GetExpiredTimer()))
call SetUnitVertexColor(data.penguin, colorRed(data.level), colorGreen(data.level), colorBlue(data.level), colorAlpha(data.level))
endfunction
//===========================================================================
private function Size takes nothing returns nothing
//code to make the penguin grow bigger and bigger !
//we also set the damage variables and all that stuff here
local MyStruct data = MyStruct(GetTimerData(GetExpiredTimer()))
local texttag text
set data.currentScale = data.currentScale + data.grow
set data.damage = data.damage + damage(data.level)
call SetUnitScale(data.penguin, data.currentScale, data.currentScale, data.currentScale)
set text = CreateTextTag()
call SetTextTagText(text, casterText(R2I(data.damage)), .023 )
call SetTextTagPosUnit( text, data.caster, 0 )
call SetTextTagColor( text, CASTER_RED, CASTER_GREEN, CASTER_BLUE, 255 )
call SetTextTagPermanent(text, false)
call SetTextTagVelocity( text, 0, .0277 )
call SetTextTagLifespan(text, 2.0)
set text = null
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
local MyStruct data
local location spellLoc
if (GetSpellAbilityId() == AID) then
//setting variables for the struct
set spellLoc = GetSpellTargetLoc()
set data = MyStruct.create(GetTriggerUnit(), GetLocationX(spellLoc), GetLocationY(spellLoc))
//this is just an eye candy, when the Stupid Penguin borns =)
//If you could find an animation with an egg, that could be cute xD
call DestroyEffect(AddSpecialEffect(BIRTH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin)))
//we attach the struct to the timer and we start it
call SetTimerData(data.growPeriod, integer(data))
call TimerStart(data.growPeriod, growTime(data.level), true, function Size)
call SetTimerData(data.colorTicker, integer(data))
call TimerStart(data.colorTicker, colorTime(data.level), true, function ChangeColor)
//cleaning up the mess
call RemoveLocation(spellLoc)
endif
set spellLoc = null // to prvent a leak
return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger StupidPenguinTrigger =CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(StupidPenguinTrigger, Condition( function Conditions ) )
set StupidPenguinTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(StupidPenguinTrigger, Condition(function onDeath))
set StupidPenguinTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(StupidPenguinTrigger, Condition(function onStop))
//setting our globals
set g = CreateGroup()
set b = Condition(function targets)
set activeTable = HandleTable.create() //Create our spell's private Table for casters and penguins
set StupidPenguinCasters = CreateGroup()
set StupidPenguins = CreateGroup()
//we preload all our effects, so they won't lag the first time we use them !=P
call Preload(EXPLOSION_EFFECT)
call Preload(DEATH_EFFECT)
call Preload(BLOOD_EFFECT)
call Preload(BIRTH_EFFECT)
set StupidPenguinTrigger = null
endfunction
endscope