package Hero
	import OnCast
	
	public module Hero
		
		abstract static function onCast(int abilId, thistype caster, int lvl)
		
		abstract static function onPointCast(int abildId, thistype caster, int lvl, vec2 tpos)
		
		abstract static function onUnitCast(int abildId, thistype caster, int lvl, unit target)
		
		abstract static function onEndCast(int abilId, thistype caster, int lvl )
		
		static function evalCast() returns boolean
			let u = GetTriggerUnit()
			let id = GetSpellAbilityId()
			let tx = GetSpellTargetX()
			let ty = GetSpellTargetY()
			let tu = GetSpellTargetUnit()
			if tx == 0 and tu == null
				onCast(id, u.getUserData() castTo thistype, GetUnitAbilityLevel(u, id) )
			else if tu == null
				onPointCast(id, u.getUserData() castTo thistype, GetUnitAbilityLevel(u, id), vec2(tx,ty))
			else
				onUnitCast(id, u.getUserData() castTo thistype, GetUnitAbilityLevel(u, id), tu)
			return false
			
		static function evalPointCast() returns boolean
			let u = GetTriggerUnit()
			let id = GetSpellAbilityId()
			onCast(id, u.getUserData() castTo thistype, GetUnitAbilityLevel(u, id) )
			return false
			
			
		static function evalEndcast() returns boolean
			let u = GetTriggerUnit()
			let id = GetSpellAbilityId()
			onEndCast(id, u.getUserData() castTo thistype, GetUnitAbilityLevel(u, id) )
			return false
		
		static function registerEvents(unit u)
			var t = CreateTrigger()
			t.registerUnitEvent(u, EVENT_UNIT_SPELL_EFFECT)
			t.addCondition(Condition(function Hero.evalCast) )
			t = CreateTrigger()
			t.registerUnitEvent(u, EVENT_UNIT_SPELL_ENDCAST)
			t.addCondition(Condition(function Hero.evalEndcast) )
endpackage
		
package HeroNAdept
	import Hero
	import Entity
	import initlater SpellEnergyBarrier
	
	// Angelica
	public class HeroNAdept extends UnitEntity
		use Hero
	
		
		construct(vec3 pos, player owner)
			super(pos, 64., CreateUnit(owner, '0000', pos.x, pos.y, 0.))
			registerEvents(actor)

		override static function onCast(int abilId, thistype caster, int lvl)
			BarrierBuff.onCast(caster, lvl)
		override static function onPointCast(int abildId, thistype caster, int lvl, vec2 tpos)
		
		override static function onUnitCast(int abildId, thistype caster, int lvl, unit target)
		
		override static function onEndCast(int abilId, thistype caster, int lvl )
endpackage

package SpellEnergyBarrier
	import Entity
	import HeroNAdept
	import TimedLoop
	import Texttag

	
	ChannelData cdata
	
	class ChannelData
		use TimedLoop
		texttag tt
		real currentAmount = 0.
		real incAmount = 0.
		UnitEntity caster
		
		construct(real inc, UnitEntity caster)
			incAmount = inc
			this.caster = caster
			tt = createTTEx(caster.getPos(), vec2(0,0), "0", 12, 10, colorA(90,200,80,200))
			startTimedLoop()
			
		override function onTimedLoop()
			currentAmount += incAmount
			tt.setText(currentAmount.toString(), 15 * 0.0023)
			
	
	public class BarrierBuff
		UnitEntity victim
		construct(UnitEntity victim, real dur, real amount)
			skip
		function defenseModifier()

	
		static function onCast(UnitEntity caster, int lvl)
			cdata = new ChannelData(lvl*1.5, caster)
	
		
		static function onEndCast(int abilId, UnitEntity caster, int lvl )
			cdata.stopTimedLoop()
			new BarrierBuff(cdata.caster, 20., cdata.currentAmount)
			
endpackage

package Entity
	import LinkedListModule
	import Terrain
	import Fx
	import Angle
	import public LinkedList
	
	/** Base Entity Class  */
	public abstract class Entity
		use LinkedListModule
		
		/** 3d vector position */
		protected vec3 pos
		/** 3d vector velocity */
		protected vec3 vel
		
		/** radius */
		real radius = 0.
		/** speed factor (multiplies to added velocity) */
		real speedFactor = 1.
		
		/** owning player */
		player owner
		
		/** flag for being static */
		boolean isStatic 	= false
		/** sleeping or active? */
		boolean active 		= true
		/** for removing */
		boolean done 		= false
		/** even if the Entites pos.z might be > 0, it still can be below terrainZ */
		boolean flying 		= false
		
	
		construct( vec3 pos, player owner )
			this.pos = pos
			this.vel = vec3(0,0,0)
			this.owner = owner

	
		construct( vec3 pos, real radius, player owner )
			this.pos = pos
			this.vel = vec3(0,0,0)
			this.owner = owner
			this.radius = radius

			
		construct( vec3 pos, vec3 vel, player owner )
			this.pos = pos
			this.vel = vel
			this.owner = owner

			
		construct( vec3 pos, vec3 vel, real radius, player owner )
			this.pos = pos
			this.vel = vel
			this.owner = owner
			this.radius = radius

			
		function getPos() returns vec3
			return pos
			
		function setPos( vec3 pos )
			this.pos = pos
			
		function addPos( vec3 pos )
			this.pos += pos
			
		function getVel() returns vec3
			return vel
			
		function setVel( vec3 vel )
			this.vel = vel
		
		function addVel( vec3 vel )
			this.vel += vel
			
		function setTarget( vec3 tpos, real speed )
			var t = pos.distanceTo2d(tpos) / speed
			let tangle = pos.angleTo2d(tpos)
			let e = getTerrainZ2(tpos.x,tpos.y)
			
			if t < 1.
				t = 1./speed
			
			let startZVelocity = ((-0.7 * t) / 2 - pos.z/t + e/t)
			this.setVel( vec3( Cos(tangle.radians()) * speed, Sin(tangle.radians()) * speed, startZVelocity) )
			
		function terminate()
			done = true
			
		/** This function is called every ANIMATION_PERIOD tick if the Entity is active */
		abstract function update()
		
	abstract public class UnitEntity extends Entity
		static trigger dmgTrig = CreateTrigger()
		protected unit actor
	
		
		construct( vec3 pos, unit actor )
			super(pos, actor.getOwner())
			this.actor = actor
			actor..setUserData(this castTo int)..addAbility(HEIGHT_ENABLER)..removeAbility(HEIGHT_ENABLER)
			dmgTrig.registerUnitEvent(actor, EVENT_UNIT_DAMAGED)
			
		construct( vec3 pos, real radius, unit actor )
			super(pos, radius, actor.getOwner())
			this.actor = actor
			actor..setUserData(this castTo int)..addAbility(HEIGHT_ENABLER)..removeAbility(HEIGHT_ENABLER)
			dmgTrig.registerUnitEvent(actor, EVENT_UNIT_DAMAGED)
			
		override function update()
			pos = vec3(actor.getX(), actor.getY(), 0.)
			
		function setNewActor(unit u)
			actor.remove()
			actor = u
			actor.setUserData(this castTo int)
			
		override function setPos(vec3 tpos)
			pos = tpos
			actor.setPos(tpos.x,tpos.y)
			actor.setFlyHeight(tpos.z - getTerrainZ2(tpos.x, tpos.y), 0)
			
		function setXY(vec3 tpos)
			pos = tpos
			actor.setXY(tpos.toVec2())
			
		static function onDamage()
			let victimData = GetTriggerUnit().getUserData() castTo UnitEntity
			let attackerData = GetAttacker().getUserData() castTo UnitEntity

			
	init
		UnitEntity.dmgTrig.addAction(function UnitEntity.onDamage)		
	
	abstract public class FxEntity extends Entity
		protected Fx fx
		
		construct( vec3 pos, real radius, player owner, angle xyAngle, string sfxpath )
			super(pos, radius, owner)
			fx = new Fx(pos, xyAngle, sfxpath)
			
		construct( vec3 pos, real radius, player owner, angle xyAngle )
			super(pos, radius, owner)
			fx = new Fx(pos, xyAngle)
		
		construct( vec3 pos, player owner, angle xyAngle )
			super(pos, 0., owner)
			fx = new Fx(pos, xyAngle)
			
		override function update()
			pos += (vel*speedFactor)
			fx.setXYZ(pos)
			
		function setFx(string sfxpath)
			fx.setFx(sfxpath)
			
		override function setPos(vec3 tpos)
			pos = tpos
			fx.setXY(tpos.toVec2())

endpackage


package OnCast
import public Vectors

public module OnPointCast
	private static trigger t = null
	private static integer spellId

	abstract static function onPointCast(unit caster, int level, vec2 target )

	private static function actions() returns boolean
		if GetSpellAbilityId() == spellId
			onPointCast(GetSpellAbilityUnit(),
				GetUnitAbilityLevel(GetSpellAbilityUnit(), spellId),
				vec2(GetSpellTargetX(), GetSpellTargetY()))
		return false

	static function registerSpellEvent(int p_spellId)
		spellId = p_spellId
		CreateTrigger()
			..registerAnyUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT)
			..addCondition(Condition(function actions))


public module OnCast
	private static trigger t = null
	private static integer spellId

	abstract static function onCast(unit caster, int level)

	private static function actions() returns boolean
		if GetSpellAbilityId() == spellId
			onCast(GetSpellAbilityUnit(),
				GetUnitAbilityLevel(GetSpellAbilityUnit(), spellId))
		return false

	static function registerSpellEvent(int p_spellId)
		spellId = p_spellId
		CreateTrigger()
			..registerAnyUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT)
			..addCondition(Condition(function actions))



public module OnUnitTargetCast
	private static trigger t = null
	private static integer spellId

	abstract static function onUnitTargetCast(unit caster, int level, unit target)

	private static function actions() returns boolean
		if GetSpellAbilityId() == spellId
			onUnitTargetCast(GetSpellAbilityUnit(),
				GetUnitAbilityLevel(GetSpellAbilityUnit(), spellId), GetSpellTargetUnit())
		return false

	static function registerSpellEvent(int p_spellId)
		spellId = p_spellId
		CreateTrigger()
			..registerAnyUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT)
			..addCondition(Condition(function actions))


endpackage

package TimedLoop

// TimedLoop
// originally created by Vexorian
// augmented by some ideas from "Constant Timer Loop 32" by Nestharus


timer timedLoopTimer = CreateTimer()
trigger timedLoopTrig = CreateTrigger()
int conditionCount = 0

function evaluateTimedLoopTrig()
	TriggerEvaluate(timedLoopTrig)
	if conditionCount == 0
		timedLoopTimer.pause()

enum Mode
	RUNNING
	PAUSED
	STOPPED
	STOPPED_FOR_GOOD


public module TimedLoop
	static private thistype array instances		// The array
	static private integer instanceCount = 0	// The count
	static private triggercondition triggerCond = null // the triggerCondition for this
	static private conditionfunc cond = Condition(function onExpire)

	// TODO this should be private but does not work currently
	private Mode mode = Mode.PAUSED

	abstract function onTimedLoop()

	private static function onExpire() returns boolean
		int i = instanceCount - 1
		while i >= 0
			thistype t = instances[i]

			t.onTimedLoop()
			if t.mode != Mode.RUNNING
				instanceCount--
				instances[i] = instances[instanceCount]
				if t.mode == Mode.STOPPED_FOR_GOOD
					t.mode = Mode.PAUSED
					destroy t
				else
					t.mode = Mode.PAUSED
			i--
		if instanceCount == 0
			TriggerRemoveCondition(timedLoopTrig, triggerCond)
			triggerCond = null
			conditionCount--
		return false

	function stopTimedLoop()
		mode = Mode.STOPPED

	function stopTimedLoopAndDestroy()
		mode = Mode.STOPPED_FOR_GOOD

	function startTimedLoop()
		instances[instanceCount] = this
		instanceCount++
		if instanceCount == 1
			if triggerCond == null
				triggerCond = TriggerAddCondition(timedLoopTrig, cond)
				conditionCount++
				if conditionCount == 1
					timedLoopTimer.startPeriodic(ANIMATION_PERIOD, function evaluateTimedLoopTrig)
		mode = Mode.RUNNING

	ondestroy
		if mode != Mode.PAUSED
			stopTimedLoop()

endpackage

package Terrain
import NoWurst
import Item

constant real		MAX_RANGE	 	= 10.
constant integer 	DUMMY_ITEM_ID 	= 'wolg'
location 			tempLoc 		= Location(0.,0.)
item	   			dItem   		= null
rect	   			find   			= null
item array 			hid
integer				hidMax 			= 0
vec2 				tempPos 		= vec2(0,0)

init
	find = Rect(0., 0., 128., 128.)
	dItem = CreateItem(DUMMY_ITEM_ID, 0, 0)
	SetItemVisible(dItem, false)

/** Returns the (not normalised) terrain-normal at the given point */
public function getTerrainNormal( real x, real y, real sampleRadius) returns vec3
	MoveLocation(tempLoc, x-sampleRadius, y)
	var zx = GetLocationZ(tempLoc)
	MoveLocation(tempLoc, x+sampleRadius, y)
	zx -= GetLocationZ(tempLoc)
	MoveLocation(tempLoc, x, y-sampleRadius)
	var zy = GetLocationZ(tempLoc)
	MoveLocation(tempLoc, x, y+sampleRadius)
	zy -= GetLocationZ(tempLoc)
	let sampleRadius2 = 2*sampleRadius
	return vec3( zx*sampleRadius2, zy*sampleRadius2, sampleRadius2*sampleRadius2 )

public function getTerrainZ2(real x, real y) returns real
	MoveLocation( tempLoc, x, y )
	return GetLocationZ( tempLoc )

public function getTerrainZ(vec2 v) returns real
	MoveLocation( tempLoc, v.x, v.y )
	return GetLocationZ( tempLoc )

public function vec2.withTerrainZ() returns vec3
	return vec3(this.x, this.y, getTerrainZ2(this.x, this.y))

public function vec2.withTerrainZ(real zoffset) returns vec3
	return vec3(this.x, this.y, getTerrainZ2(this.x, this.y) + zoffset)

public function vec3.withTerrainZ() returns vec3
	return vec3(this.x, this.y, getTerrainZ2(this.x, this.y))

public function vec3.withTerrainZ(real zoffset) returns vec3
	return vec3(this.x, this.y, getTerrainZ2(this.x, this.y) + zoffset)

public function isTerrainDeepWater(real x, real y) returns boolean
	return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)

public function isTerrainShallowWater(real x, real y) returns boolean
	return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)

public function isTerrainLand(real x, real y) returns boolean
	return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)

public function isTerrainPlatform(real x, real y) returns boolean
	return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)

function hideItem()
	if IsItemVisible(GetEnumItem())
		hid[hidMax] = GetEnumItem()
		SetItemVisible(hid[hidMax], false)
		hidMax++

public function isTerrainWalkable(real x, real y) returns boolean
	//Hide any items in the area to avoid conflicts with our item
	MoveRectTo(find, x, y)
	EnumItemsInRect(find ,null, function hideItem)
	//Try to move the test item and get its coords
	SetItemPosition(dItem, x, y) //Unhides the item
	tempPos = dItem.getPos()
	SetItemVisible(dItem, false)//Hide it again
	//Unhide any items hidden at the start
	while hidMax > 0
		hidMax--
		SetItemVisible(hid[hidMax], true)
		hid[hidMax] = null
	//Return walkability
	return (tempPos.x-x)*(tempPos.x-x)+(tempPos.y-y)*(tempPos.y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)

endpackage