

class BuffHandler : EventHandler {

	BuffThinker buffThinker;

	override void WorldLoaded(WorldEvent e) {
		buffThinker = BuffThinker.getInstance();
	}
}

class BuffThinker : Thinker {

	array<Actor> buffedEnemies;
	array<int> buffTimers;
	
	BuffThinker init() {
		ChangeStatNum(STAT_USER);
		return self;
	}
	
	static BuffThinker getInstance() {
		ThinkerIterator it = ThinkerIterator.Create("BuffThinker", STAT_USER);
		Thinker t = it.Next();
		if (t) {
			return BuffThinker(t);
		}
		return new("BuffThinker").init();
	}
	
	void buffEnemies(Actor buffingActor, int buffLevel, int range) {
		Actor target;
		int i;
		int f;
		for(let it = BlockThingsIterator.Create(buffingActor, range); it.next();) {
			target = it.thing;
			if (target.bISMONSTER == false || target.health <= 0) {
				continue;
			}
			if (buffingActor.Distance3D(target) <= range) {
				i = buffedEnemies.find(target);
				if (i == buffedEnemies.Size()) {
					target.damageFactor = target.damageFactor * .8;
					buffedEnemies.Push(target);
					buffTimers.Push(525);
					if (target.target != null && target.target.bISMONSTER == true) {
						target.target = null;
					}
				} else {
					buffTimers[i] = 525;
					if (target.target != null && target.target.bISMONSTER == true) {
						target.target = null;
					}
				}
			}
		}
		
	}

	override void Tick() {
		super.Tick();
		
		for (int i = 0; i < buffedEnemies.Size(); i++) {
			Actor enemy = buffedEnemies[i];
			if (enemy == null) {
				//Console.printf("found nothing!");
				buffedEnemies.delete(i);
				buffTimers.delete(i);
				i--;
				continue;
			}
			if (buffTimers[i] > 0 && enemy.health > 0) {
				//write to address 0?
				enemy.bJUSTHIT = true;
				enemy.giveBody(1);
				//Console.printf("target has %d hp", enemy.Health);
				if (random(1, 32) == 8) {
					Actor flame = Actor.Spawn("BuffFlameNoot", enemy.pos + (0, 0, frandom(0, enemy.height)));
					vector3 spawnVel = (frandom(-1, 1), frandom(-1, 1), frandom(.1, 2));
					flame.vel = spawnVel;
				}
				buffTimers[i] -= 1;
			} else {
				enemy.damageFactor = enemy.damageFactor * 1.25;
				buffedEnemies.delete(i);
				buffTimers.delete(i);
				i--;
			}
		}
	}
}

class BuffFlameNoot : Actor {

	Default {
		Mass 5;
		+NOBLOCKMAP
		+NOTELEPORT
		+NOGRAVITY
		+ALLOWPARTICLES
		+BRIGHT
	}
	
	States
	{
		Spawn:
			BFFL ABCDEF 8;
			BFFL HIHIHIHI 2;
			Stop;
	}
}

class BuffWizard : Actor {

	int buffWait;

	Default {
		Health 400;
		Radius 20;
		Height 56;
		Mass 350;
		Speed 8;
		PainChance 200;
		Monster;
		MaxTargetRange 1;
		+FLOORCLIP
		SeeSound "imp/sight";
		PainSound "imp/pain";
		DeathSound "imp/death";
		ActiveSound "imp/active";
		HitObituary "$OB_IMPHIT";
		Obituary "$OB_IMP";
		Tag "$TAG_Buff Wizard";
	}
	States
	{
		Spawn:
			BWZ2 AB 10 A_Look;
			Loop;
		See:
			BWZ2 AAABBB 2 A_JumpIf(BuffChase(), "Buff");
			Loop;
		Buff:
			BWZ2 D 0 BRIGHT;
			BWZ2 D 2 BRIGHT;
			BWZ2 D 2 BRIGHT;
			BWZ2 EXEXEX 8 BRIGHT A_FaceTarget;
			BWZ2 F 16 BRIGHT BuffEnemies();
			BWZ2 C 20 BRIGHT;
			Goto See;
		Pain:
			BWZ2 C 5 A_Pain;
			BWZ2 C 5;
			BWZ2 ABAB 2;
			Goto See;
		Death:
			BWZ2 H 7;
			BWZ2 I 7 A_Scream;
			BWZ2 K 7 A_NoBlocking;
			BWZ2 LMNOP 7;
			BWZ2 P -1;
			Stop;
		Raise:
			BWZ2 YXWVUTSR 7;
			Goto See;
	}
	
	Bool BuffChase() {
		A_Chase();
		if (buffWait > 0) {
			return false;
		}
		buffWait = random(150, 250);
		let it = BlockThingsIterator.Create(self, 512);
		
		for(;it.next();) {
			target = it.thing;
			if (target.bISMONSTER == true && checkSight(target)) {
				self.target = target;
				return true;
			}
		}
		return true;
	}
	
	void BuffEnemies() {
		BuffThinker bT = BuffThinker.getInstance();
		bT.buffEnemies(self, 1, 416);
	}
	
	override void Tick() {
		super.Tick();
		//Console.printf("%d", buffWait);
		buffWait--;
	}
}

Class PowerDamageQuad : PowerDamage
{
	Default
	{
		Damagefactor "normal", 4;
		powerup.duration 1050;
	}
	
	override void InitEffect() 
	{
		super.InitEffect();
	}
	
	override void EndEffect ()
	{
		super.EndEffect();
	}
}

Class QuadDamage : PowerupGiver
{
	Default
	{
		inventory.pickupmessage "Quad Damage";
		inventory.maxamount 0;
		inventory.usesound "pickups/slowmo";
		powerup.type "PowerDamageQuad";
		powerup.color "255, 0, 0", 0.5;
		translation "128:143=120:127";
		+INVENTORY.AUTOACTIVATE;
		+INVENTORY.ALWAYSPICKUP;
		+INVENTORY.FANCYPICKUPSOUND;
		Tag "$TAG_Quad Damage";
	}
	States
    {
	Spawn:
		QUAD ABCDB 3 bright;
		loop;
	}
}

class BFG9005 : DoomWeapon
{
	Default
	{
		Weapon.SlotNumber 7;
		Height 20;
		Weapon.SelectionOrder 2800;
		Weapon.AmmoUse 40;
		Weapon.AmmoGive 40;
		Weapon.AmmoType "Cell";
		+WEAPON.NOAUTOFIRE;
		Inventory.PickupMessage "$GOTBFG9005";
		Tag "$TAG_BFG9005";
	}
	States
	{
	Ready:
		BFGG A 1 A_WeaponReady;
		Loop;
	Deselect:
		BFGG A 1 A_Lower;
		Loop;
	Select:
		BFGG A 1 A_Raise;
		Loop;
	Fire:
		BFGG A 20 A_BFGsound;
		BFGG B 10 A_GunFlash;
		BFGG B 10 A_FireBFG9005();
		BFGG B 20 A_ReFire;
		Goto Ready;
	Flash:
		BFGF A 11 Bright A_Light1;
		BFGF B 6 Bright A_Light2;
		Goto LightDone;
	Spawn:
		BFUG A -1;
		Stop;
	}
	
	action void A_FireBFG9005() {
		A_FireProjectile("BFG9005Ball", 0, 1);
	}
}

class BFG9005Ball : Actor
{
	Default
	{
		Radius 13;
		Height 8;
		Speed 25;
		Damage 100;
		Projectile;
		+RANDOMIZE
		+ZDOOMTRANS
		RenderStyle "Add";
		Alpha 0.75;
		DeathSound "weapons/bfgx";
		Obituary "$OB_MPBFG_BOOM";
	}
	States
	{
	Spawn:
		BFS1 AB 4 Bright;
		Loop;
	Death:
		BFE1 AB 8 Bright;
		BFE1 C 8 Bright A_BFG9005Spray();
		BFE1 DEF 8 Bright;
		Stop;
	}
	
	void A_BFG9005Spray(class<Actor> spraytype = "BFGExtra", int numrays = 40, int damagecnt = 15, double ang = 90, double distance = 16*64, double vrange = 32, int defdamage = 0, int flags = 0) {
		int damage;
		FTranslatedLineTarget t;

		// validate parameters
		if (spraytype == null) spraytype = "BFGExtra";
		if (numrays <= 0) numrays = 40;
		if (damagecnt <= 0) damagecnt = 15;
		if (ang == 0) ang = 90.;
		if (distance <= 0) distance = 16 * 64;
		if (vrange == 0) vrange = 32.;

		// [RH] Don't crash if no target
		if (!target) return;

		// [XA] Set the originator of the rays to the projectile (self) if
		//      the new flag is set, else set it to the player (target)
		Actor originator = target;

		// offset angles from its attack ang
		for (int i = 0; i < numrays; i++)
		{
			//Console.printf("==== %d ====", i);
			double an = angle - ang / 2 + ang / numrays*i;
			
			if (defdamage == 0) {
				damage = 0;
					for (int j = 0; j < damagecnt; ++j)
					damage += Random[BFGSpray](1, 8);
				}
			else
			{
				// if this is used, damagecnt will be ignored
				damage = defdamage;
			}

			originator.AimLineAttack(an, distance, t, vrange);
			
			while (t.linetarget != null && damage > 1) {
				//Console.printf("%d damage remaining", damage);
				Actor spray = Spawn(spraytype, t.linetarget.pos + (0, 0, t.linetarget.Height / 4), ALLOW_REPLACE);

				int dmgFlags = 0;
				Name dmgType = 'BFGSplash';

				if (spray != null)
				{
					if ((spray.bMThruSpecies && target.GetSpecies() == t.linetarget.GetSpecies()) || 
						(!(flags & BFGF_HURTSOURCE) && target == t.linetarget)) // [XA] Don't hit oneself unless we say so.
					{
						spray.Destroy(); // [MC] Remove it because technically, the spray isn't trying to "hit" them.
						continue;
					}
					if (spray.bPuffGetsOwner) spray.target = target;
					if (spray.bFoilInvul) dmgFlags |= DMG_FOILINVUL;
					if (spray.bFoilBuddha) dmgFlags |= DMG_FOILBUDDHA;
					dmgType = spray.DamageType;
				}

				int healthy = t.linetarget.health;
				int realdam = t.linetarget.DamageMobj(originator, target, damage, dmgType, dmgFlags|DMG_USEANGLE, t.angleFromSource);
				t.TraceBleed(realdam > 0 ? realdam : damage, self);
				
				int donedam = (t.linetarget.health > 0) ? healthy - t.linetarget.health : healthy;
				//console.printf("%d dd, %d rd", donedam, realdam);
				damage = damage - (damage*(1.0*donedam/realdam));
				
				originator.AimLineAttack(an, distance, t, vrange);
			}
		}
	}
}

class EviSmallTree01 : Actor {

	Default {
		+SOLID
		Radius 20;
		Height 160;
		Tag "$TAG_Evi Small Tree 1";
	}
	States
	{
		Spawn:
			NTC1 A -1;
			Stop;
	}
}

class EviSmallTree02 : Actor {


	Default {
		+SOLID
		Radius 20;
		Height 128;
		Tag "$TAG_Evi Small Tree 2";
	}
	States
	{
		Spawn:
			NTC2 A -1;
			Stop;
	}
}

class EviBigTree01 : Actor {

	Default {
		+SOLID
		Radius 20;
		Height 160;
		Tag "$TAG_Evi Big Tree 1";
	}
	States
	{
		Spawn:
			NTC4 A -1;
			Stop;
	}
}

class EviBigTree02 : Actor {


	Default {
		+SOLID
		Radius 20;
		Height 160;
		Tag "$TAG_Evi Big Tree 2";
	}
	States
	{
		Spawn:
			NTC3 A -1;
			Stop;
	}
}