/*
	BigVolcano
	Author: Sven2

	DESC
*/

static const BigVolcanoBehaviour_Fill = 0,
             BigVolcanoBehaviour_Advance = 1,
             BigVolcanoBehaviour_AdvanceLava = 2,
             BigVolcanoBehaviour_Stop = 3;
             
static const BigVolcano_XRes = 25; // step size of segments of lava_y array

local lava_y; // array for current vertical lava positions
local lava_y_endpoint; // volcano is done when lava reaches this point
local n_lava_y; // size of lava_y array
local n_branches;
local mat_behaviours; // array of BigVolcanoBehaviour_*, indexed by GetMaterial(x,y)+1: How to behave in materials
local mat_advancespeeds; // array of ints from 0 to 100, indexed by GetMaterial(x,y)+1: How to behave in materials

func Activate(int start_y, int end_y)
{
	if (lava_y) return; // already active
	if (!start_y) start_y = LandscapeHeight();
	if (!end_y) end_y = 0;
	lava_y_endpoint = end_y;
	ScheduleCall(this, this.Execute, 10, 99999);
	n_branches = 0;
	mat_behaviours = CreateArray(20);
	mat_advancespeeds = CreateArray(20);
	mat_behaviours[0] = BigVolcanoBehaviour_Fill;
	mat_advancespeeds[0] = 80;
	for (var mat_idx=0, mat_name; mat_name=MaterialName(mat_idx); ++mat_idx)
	{
		var behaviour, density = GetMaterialVal("Density", "Material", mat_idx), speed;
		if (WildcardMatch(mat_name, "*Lava")) // pass through lava
		{
			behaviour = BigVolcanoBehaviour_AdvanceLava;
			speed = 100;
		}
		else if (WildcardMatch(mat_name, "*Water")) // fill into water to remove it
		{
			behaviour = BigVolcanoBehaviour_Fill;
			speed = 10;
		}
		else if (density <= 0) // fill tunnels
		{
			behaviour = BigVolcanoBehaviour_Fill;
			speed = 80;
		}
		else if (density <= 25) // pass through any other liquids
		{
			behaviour = BigVolcanoBehaviour_Advance;
			speed = 60;
		}
		else if (GetMaterialVal("DigFree", "Material", mat_idx)) // pass through diggable materials
		{
			behaviour = BigVolcanoBehaviour_Advance;
			speed = 50;
		}
		else // stop at rocks + minerals
		{
			behaviour = BigVolcanoBehaviour_Stop;
			speed = 10;
		}
		mat_behaviours[mat_idx+1] = behaviour;
		mat_advancespeeds[mat_idx+1] = speed;
	}
	n_lava_y = (LandscapeWidth()-1)/BigVolcano_XRes+2;
	lava_y = CreateArray(n_lava_y);
	for (var i=0; i<n_lava_y; ++i) lava_y[i] = start_y;
	return true;
}

func Execute()
{
	//Log("tic");
	// Create branch
	var ls_w=LandscapeWidth();
	if (n_branches<20 && !Random(1))
	{
		var x = Random(ls_w);
		LaunchBranch(x, GetLavaY(x), Random(5)-2, -15-Random(5));
	}
	// Rise lava
	if (true)
	{
		var any_nonlava, last_move, this_move;
		for (var i=0; i<n_lava_y; ++i)
		{
			if (lava_y[i] > lava_y_endpoint)
			{
				any_nonlava = true;
				var x = BoundBy(i*BigVolcano_XRes-BigVolcano_XRes/2, 0, ls_w-BigVolcano_XRes) + Random(BigVolcano_XRes);
				var y = GetLavaY(x);
				var speed = mat_advancespeeds[GetMaterial(x,y-5)+1];
				if (i) if (lava_y[i] > lava_y[i-1]+BigVolcano_XRes*2) speed+=50;
				if (i<n_lava_y-1) if (lava_y[i] > lava_y[i+1]+BigVolcano_XRes*2) speed+=50;
				this_move = (Random(100) < speed);
				if (this_move)
					--lava_y[i];
				else if (!Random(3))
				{
					if (speed<=10)
					{
						// Blast away solid
						var blast_size = 5+Random(5);
						BlastFree(x,y-3,blast_size,GetController());
						// gfx
						CreateParticle("MagicFire", x,y-3, 0,0, blast_size*5, 0xffffffff);
						if (!Random(5)) SoundAt("RockHit*", x,y-3, 100);
					}
					else if (speed <=50)
					{
						// Crumble away diggable
						ShakeFree(x,y-Random(20),5);
					}
				}
				if (last_move || (this_move && i))
				{
					DrawMaterialQuad("DuroLava-lava_red", (i-1)*BigVolcano_XRes,lava_y[i-1], i*BigVolcano_XRes,lava_y[i], i*BigVolcano_XRes,lava_y[i]+2, (i-1)*BigVolcano_XRes,lava_y[i-1]+2, true);
				}
				last_move = this_move;
			}
			
		}
		// Lava risen to the max?
		if (!any_nonlava)
		{
			ClearScheduleCall(this, this.Execute);
			return;
		}
	}
	return true;
}

func GetLavaY(int x)
{
	var i = BoundBy(x/BigVolcano_XRes, 0, n_lava_y-2), ix = x%BigVolcano_XRes;
	return (lava_y[i]*(BigVolcano_XRes-ix) + lava_y[i+1]*ix) / BigVolcano_XRes;
}


/* Branch effects */
// draws a lava branch that walks ahead of the rising main lava lake
// branch effect variables:
// x,y: Branch tip position
// tip_progress: Progress from trace_x/y[end] towards dir_x/dir_y. From 0 to 100.
// dir_x,dir_y: Advance direction per segment
// len: Length of branch
// trace_x,trace_y: Array of previous positions. Index from oldest to newest. Currenct tip x/y not included.
// trace_len: length of trace_x,trace_y arrays

local dbg_counter;

func LaunchBranch(int x, int y, int dir_x, int dir_y, int parent_level)
{
	var fx = AddEffect("VolcanoBranch", this, 1, 3, this);
	fx.x=x; fx.y=y; fx.dir_x=dir_x; fx.dir_y=dir_y;
	fx.tip_progress=0;
	fx.trace_x=[x]; fx.trace_y=[y]; fx.trace_len=fx.len=1;
	fx.counter = ++dbg_counter;
	fx.level = parent_level + 1;
	//Log("LaunchBranch %d from %d/%d towards %d/%d", fx.counter,x,y,dir_x,dir_y);
	++n_branches;
	return fx;
}

func FxVolcanoBranchTimer(object q, fx, int time)
{
	// Progress tip
	var next_tip_progress = fx.tip_progress + 10;
	var last_segment = fx.trace_len-1;
	var sx=fx.trace_x[last_segment], sy=fx.trace_y[last_segment];
	var nx=sx+next_tip_progress*(fx.dir_x)/100, ny=sy+next_tip_progress*(fx.dir_y)/100;
	//Log("Go %d from %d/%d towards %d/%d", fx.counter,sx,sy,nx-sx,ny-sy);
	if (nx==sx && ny==sy) { fx.tip_progress=next_tip_progress; return FX_OK; }
	if (ny<0 || fx.len>20) return FX_Execute_Kill; // End here?
	var behaviour = mat_behaviours[GetMaterial(nx,ny)+1];
	var i,dx,dy;
	if (behaviour == BigVolcanoBehaviour_Fill)
	{
		CastPXS("DuroLava", 4+Cos(fx.fill_time*40,2), 30+Cos(fx.fill_time*40,25), nx,ny-1, 0,50);
		if (!(fx.fill_time%20) && !GBackSemiSolid(nx,ny-6)) SoundAt("BigVolcanoBubble*", nx,ny, 10);
		if (fx.fill_time++ > 31) return FX_Execute_Kill; // Done?
	}
	else if (behaviour != BigVolcanoBehaviour_Stop)
	{
		fx.fill_time = 0;
		//if (behaviour == BigVolcanoBehaviour_AdvanceLava && !Random(5)) return FX_Execute_Kill; // prevent too many overlapping branches
		DrawVerticalBranch(fx.x,fx.y,nx,ny,1);
		fx.x=nx; fx.y=ny;
		// Extend width of old segments
		for (i=fx.tip_progress*last_segment/100; i<next_tip_progress*last_segment/100; ++i)
		{
			var old_half_wdt = (last_segment-i+1)/3+1;
			var new_half_wdt = (last_segment-i+2)/3+1;
			if (new_half_wdt != old_half_wdt)
				ExtendVerticalBranch(fx.trace_x[i],fx.trace_y[i], fx.trace_x[i+1],fx.trace_y[i+1], old_half_wdt, new_half_wdt);
		}
		fx.tip_progress = next_tip_progress;
	}
	else
	{
		// Do not bounce too much
		if (++fx.bounces>6) return FX_Execute_Kill;
		// Try to find a new spot to advance to
		if (fx.tip_progress)
		{
			// Start searching from last pos we went to
			sx+=fx.tip_progress*(fx.dir_x)/100;
			sy+=fx.tip_progress*(fx.dir_y)/100;
		}
		dx = Sign(fx.dir_x);
		if (!dx) dx = Random(2)*2-1;
		dx *= 5;
		var n_valid_branches=0;
		for (i=0; i<2; ++i)
		{
			for (dy=-10; dy<10; dy+=2)
			{
				var b = mat_behaviours[GetMaterial(sx+dx,sy+dy)+1];
				if (b != BigVolcanoBehaviour_Stop && b != BigVolcanoBehaviour_AdvanceLava)
				{
					if (!n_valid_branches++)
					{
						// First valid branch: Take over this one
						nx = sx+dx;
						ny = sy+dy;
						// Do not branch too deeply
						if (fx.level>1) ++i;
					}
					else
					{
						// Second possible position: Branch off this one
						LaunchBranch(sx,sy,dx,dy,fx.level++);
					}
					break;
				}
			}
			dx*=-1;
		}
		// Caught in a corner?
		if (!n_valid_branches) return FX_Execute_Kill;
		// Spot found. Add segment if we already progressed in the last segment
		/*if (fx.tip_progress)
		{
			fx.trace_x[fx.trace_len] = sx; fx.trace_y[fx.trace_len] = sy;
			++fx.trace_len; ++fx.len;
			fx.tip_progress = 0;
		}*/
		// Move to new spot
		fx.dir_x = nx-fx.trace_x[last_segment];
		fx.dir_y = ny-fx.trace_y[last_segment];
	}
	// End pos reached?
	if (fx.tip_progress==100)
	{
		// Add new tip
		fx.trace_x[fx.trace_len] = nx; fx.trace_y[fx.trace_len] = ny;
		++fx.trace_len; ++fx.len;
		// Launch next branch
		fx.tip_progress = 0;
		fx.dir_x = BoundBy(fx.dir_x+Random(5)-2,-8,8);
		fx.dir_y = BoundBy(fx.dir_y+Random(5)-2,-15,-20);
		//Log("%v %v",fx.trace_y, fx.trace_len);
	}
	// Remove segments that fell below the main lake (max one at a time)
	if (fx.trace_y[1] >= GetLavaY(fx.trace_x[1]))
	{
		fx.trace_x = fx.trace_x[1:fx.trace_len];
		fx.trace_y = fx.trace_y[1:fx.trace_len];
		--fx.trace_len;
	}
	return FX_OK;
}

func FxVolcanoBranchStop(object q, fx, int reason, bool temp)
{
	if (!temp)
	{
		--n_branches;
		//Log("DieBranch %d", fx.counter);
	}
	return FX_OK;
}

func DrawVerticalBranch(int x1, int y1, int x2, int y2, int half_wdt)
{
	// Draw branch from x1/y1 to x2/y2 with width half_wdt*2
	//Log("BRANCH %d,%d,%d,%d, %d",x1,y1,x2,y2,half_wdt);
	if (Abs(x2-x1)>Abs(y2-y1))
		return DrawMaterialQuad("DuroLava-lava_red",x1,y1-half_wdt, x1,y1+half_wdt, x2,y2+half_wdt, x2,y2-half_wdt, true);
	else
		return DrawMaterialQuad("DuroLava-lava_red",x1-half_wdt,y1, x1+half_wdt,y1, x2+half_wdt,y2, x2-half_wdt,y2, true);
}

func ExtendVerticalBranch(int x1, int y1, int x2, int y2, int from_half_wdt, int to_half_wdt)
{
	//Log("EXTBRANCH %d,%d,%d,%d, %d->%d",x1,y1,x2,y2,from_half_wdt,to_half_wdt);
	// Extend width of drawn branch from x1/y1 to x2/y2 from width from_half_wdt*2 to to_half_wdt*2
	// Effectively draws extra branch left and right, unless the branch had been pretty thin before
	if (from_half_wdt <= 2) return DrawVerticalBranch(x1,y1,x2,y2,to_half_wdt);
	if (Abs(x2-x1)>Abs(y2-y1))
	{
		DrawMaterialQuad("DuroLava-lava_red",x1,y1-to_half_wdt, x1,y1-from_half_wdt, x2,y2-from_half_wdt, x2,y2-to_half_wdt, true);
		DrawMaterialQuad("DuroLava-lava_red",x1,y1+to_half_wdt, x1,y1+from_half_wdt, x2,y2+from_half_wdt, x2,y2+to_half_wdt, true);
	}
	else
	{
		DrawMaterialQuad("DuroLava-lava_red",x1-to_half_wdt,y1, x1-from_half_wdt,y1, x2-from_half_wdt,y2, x2-to_half_wdt,y2, true);
		DrawMaterialQuad("DuroLava-lava_red",x1+to_half_wdt,y1, x1+from_half_wdt,y1, x2+from_half_wdt,y2, x2+to_half_wdt,y2, true);
	}
	return true;
}

func SoundAt(string sound_name, int x, int y, int vol)
{
	var sound_host = CreateObject(BigVolcano,x,y), r;
	if (sound_host)
	{
		r = sound_host->Sound(sound_name, false, vol);
		ScheduleCall(sound_host, Global.RemoveObject, 100);
	}
	return r;
}

local Name = "BigVolcano";
local Description = "Volcano helper object";
