
function LEVEL_START()
{
	global level_portals = [];
	global gSGR_BulletWorld = Bullet_CreateWorld();
	global max_see_time = 0.0;
	global time_since_saved = 9999.0;
	global current_cutscene = null;
	global mission_complete = false;
	
	global music_ambient = gSGR_SoundSystem.create_emitter( "music/amb.ogg" );
	music_ambient.loop = true;
	music_ambient.groups = SOUNDTYPE_MUSIC;
	music_ambient.play();
	
	PauseScreen.init();
	
	global gSGR_OnGlobalSerialize = LEVEL_OnGlobalSerialize;
	global gSGR_OnGlobalUnserialize = LEVEL_OnGlobalUnserialize;
}

function LEVEL_OnGlobalSerialize()
{
	return
	{
		max_see_time = max_see_time,
		current_cutscene = @current_cutscene.id,
		mission_complete = mission_complete,
		fogColor = gSGR_Scene.fogColor,
		fogDensity = gSGR_Scene.fogDensity,
		camera_zfar = gSGR_Scene.camera.zfar,
	};
}

function LEVEL_OnGlobalUnserialize( globdata )
{
	global max_see_time = globdata.max_see_time;
	global current_cutscene = @gSGR_World.entities[ globdata.current_cutscene ];
	global mission_complete = globdata.mission_complete;
	gSGR_Scene.fogColor = globdata.fogColor;
	gSGR_Scene.fogDensity = globdata.fogDensity;
	gSGR_Scene.camera.zfar = globdata.camera_zfar;
}

function SAVE_GAME()
{
	if( SGR_SerializeState() )
	{
		global time_since_saved = 0.0;
		println( "serialization successful!" );
	}
	else
		ERROR( "serialization has failed: " $ gSGR_LastStateError );
}

function UPDATE_LEVEL( dt )
{
	mst = 0.0;
	gSGR_BulletWorld.stepSimulation( dt, 0 );
	foreach( ent : gSGR_World.entities )
	{
		if( @ent.seetime )
			mst = max( mst, ent.seetime );
	}
	global max_see_time = mst;
	
	cp = gSGR_Scene.camera.position;
	cd = gSGR_Scene.camera.direction;
	cu = gSGR_Scene.camera.up;
	gSGR_SoundSystem.set_listener_position( cp.x, cp.y, cp.z );
	gSGR_SoundSystem.set_listener_orientation( cd.x, cd.y, cd.z, cu.x, cu.y, cu.z );
	
	global time_since_saved;
	time_since_saved += dt;
}

function INSIDE_PORTAL( x, y, z )
{
	pos2 = vec2( x, y );
	foreach( portal : level_portals )
	{
		if( ( portal.pos2 - pos2 ).length < portal.r )
			return if( z > portal.z, -portal.h, portal.h );
	}
	return 0;
}

function ENTITY_CREATE_portal( params )
{
	Portal =
	{
		x = params.x,
		y = params.y,
		z = params.z,
		d = @params.d || 2.88,
		h = @params.h || 18.432,
		pos2 = vec2( params.x, params.y ),
		time = 0.0,
	};
	Portal.r = Portal.d / 2;
	
	mesh = gSGR_GetMesh( "portal" );
	
	mi = gSGR_Scene.createMeshInstance();
	mi.mesh = mesh;
	mi.matrix = mat4().scale( Portal.r, Portal.r, 1 ).translate_v3( vec3( Portal.x, Portal.y, Portal.z + Portal.h * 0.5 ) );
	Portal.mesh_inst1 = mi;
	
	mi = gSGR_Scene.createMeshInstance();
	mi.mesh = mesh;
	mi.matrix = mat4().scale( Portal.r, Portal.r, 1 ).translate_v3( vec3( Portal.x, Portal.y, Portal.z - Portal.h * 0.5 ) );
	Portal.mesh_inst2 = mi;
	
	function Portal.serialize()
	{
		return "portal",
		{
			x = this.x,
			y = this.y,
			z = this.z,
			d = this.d,
			h = this.h,
		},
		{
			time = this.time,
		};
	}
	
	function Portal.unserialize( data )
	{
		this.time = data.time;
	}
	
	function Portal.tick( dt )
	{
		this.time += dt;
		this.mesh_inst1.setConstF( 0, this.time );
		this.mesh_inst2.setConstF( 0, this.time );
	}
	
	function Portal.added()
	{
		level_portals.push( this );
	}
	
	function Portal.removed()
	{
		level_portals.remove( this );
	}
	
	return Portal;
}

function ENTITY_CREATE_turret( params )
{
	Turret =
	{
		orig_position = params.position,
		position = params.position + vec3(0,0,0.5),
		direction = params.direction.normalized,
		bliptime = 0.0,
		
		see = false,
		seetime = 0.0,
		shoot_timeout = 0.0,
		flash_timeout = 0.0,
		shoot_acc = 0.0,
		
		enabled = true,
		
		_use_test = null,
		_use_act = null,
	};
	Turret.gun_origin = Turret.position + vec3(0,0,0.3);
	Turret.gun_dir = Turret.direction;
	
	mesh_base = gSGR_GetMesh( "turretbase" );
	mesh_gun = gSGR_GetMesh( "turretgun" );
	mesh_flash = gSGR_GetMesh( "turretflash" );
	
	mi = gSGR_Scene.createMeshInstance();
	mi.mesh = mesh_base;
	mi.matrix = mat4().scale(0.7,0.7,0.7).rotateZ( -M_PI/2-vec2(Turret.direction.x,Turret.direction.y).angle ).translate_v3( Turret.position );
	Turret.mi_base = mi;
	
	mi = gSGR_Scene.createMeshInstance();
	mi.mesh = mesh_gun;
	mi.matrix = mat4().scale(0.7,0.7,0.7).rotateZ( -M_PI/2-vec2(Turret.direction.x,Turret.direction.y).angle ).translate_v3( Turret.gun_origin );
	Turret.mi_gun = mi;
	
	mi = gSGR_Scene.createMeshInstance();
	mi.mesh = mesh_flash;
	mi.matrix = mat4().scale(0.7,0.7,0.7).rotateZ( -M_PI/2-vec2(Turret.direction.x,Turret.direction.y).angle ).translate_v3( Turret.gun_origin );
	Turret.mi_flash = mi;
	
	sprite_mesh = gSGR_SS3D.createMesh( true ).{ numParts = 1, transparent = true };
	sprite_mesh.setPartShader( 0, "sprite" );
	sprite_mesh.setPartTexture( 0, 0, gSGR_SS3D.getTexture( "rsrc/lightsprite.png" ) );
	sprite_mesh.setPartTexture( 0, 1, gSGR_SS3D.getTexture( "rsrc/normal.png" ) );
	sprite_mesh_inst = gSGR_Scene.createMeshInstance().{ mesh = sprite_mesh };
	sprite_mesh_inst.setConstV3( 1, vec3( 0.5 ) );
	Turret.{ sprite_mesh = sprite_mesh, sprite_mesh_inst = sprite_mesh_inst };
	
	// sfx - shoot
	snd = gSGR_SoundSystem.create_emitter( "sounds/turret_shoot.wav" );
	snd.groups = SOUNDTYPE_SFX;
	snd.factor = 100;
	Turret.sfx_shoot = snd;
	
	// physics
	sh_capsuleZ = Bullet_CreateCapsuleShapeZ( 0.25, 0.75 );
	rbci = Bullet_CreateRigidBodyCreationInfo();
	rbci.collisionShape = sh_capsuleZ;
	rbci.position = Turret.position;
	body = Bullet_CreateRigidBody( rbci );
	gSGR_BulletWorld.addRigidBody( body, 0x04 );
	Turret.body = body;
	
	function Turret.serialize()
	{
		return "turret",
		{
			position = this.orig_position,
			direction = this.direction,
		},
		{
			bliptime = this.bliptime,
			see = this.see,
			seetime = this.seetime,
			shoot_timeout = this.shoot_timeout,
			flash_timeout = this.flash_timeout,
			shoot_acc = this.shoot_acc,
			enabled = this.enabled,
		};
	}
	
	function Turret.unserialize( data )
	{
		this.bliptime = data.bliptime;
		this.see = data.see;
		this.seetime = data.seetime;
		this.shoot_timeout = data.shoot_timeout;
		this.flash_timeout = data.flash_timeout;
		this.shoot_acc = data.shoot_acc;
		this.enabled = data.enabled;
	}
	
	function Turret._use_test( pos )
	{
		len = ( pos - this.position ).length;
		if( this.enabled && len < 0.8 )
			return "disable turret", len;
	}
	
	function Turret._use_act()
	{
		this.enabled = false;
	}
	
	function Turret.removed()
	{
		gSGR_BulletWorld.removeRigidBody( this.body );
	}
	
	function Turret.shoot()
	{
		if( this.shoot_timeout > 0 )
			return;
		this.shoot_timeout = 0.2;
		this.flash_timeout = 0.06;
		if( this.see )
		{
			this.shoot_acc += clamp( 10 / pow( ( this.gun_origin - PLAYER.position ).length, 1.3 ), 0, 1 );
			if( this.shoot_acc > 1 )
			{
				PLAYER.hit();
				this.shoot_acc -= 1;
			}
		}
		
		this.sfx_shoot.stop();
		this.sfx_shoot.set_position( this.gun_origin.x, this.gun_origin.y, this.gun_origin.z );
		this.sfx_shoot.play();
	}
	
	function Turret.updateSeeTime( dt )
	{
		mp = this.position;
		pp = PLAYER.position;
		if( this.enabled )
		{
			see = ( mp - pp ).length < 10;
			if( see )
			{
				see = vec3_dot( ( pp - mp ).normalized, this.gun_dir ) > 0.7;
				if( see )
				{
					(rr,cs,rn,rp) = gSGR_BulletWorld.rayTestClosest( mp, pp, 0x01, 0x01 );
					see = rr === null;
				}
			}
		}
		else see = false;
		this.see = see;
		
		if( !this.enabled )
			this.seetime = 0;
		if( see )
			this.seetime += dt;
		else
			this.seetime -= dt * 2;
		this.seetime = clamp( this.seetime, 0, 5 );
		if( this.seetime > 1 )
			this.shoot();
		
		if( this.enabled )
		{
			lind = lerp( this.direction, vec3( pp.x - mp.x, pp.y - mp.y, 0 ).normalized, clamp( this.seetime, 0, 1 ) ).normalized;
			this.mi_flash.matrix = this.mi_gun.matrix = mat4().scale(0.7,0.7,0.7).rotateZ( -M_PI/2-vec2(lind.x,lind.y).angle ).translate_v3( this.gun_origin );
			this.gun_dir = lind;
		}
		
		this.shoot_timeout -= dt;
		this.flash_timeout -= dt;
		this.mi_flash.enabled = this.flash_timeout > 0.0;
	}
	
	function Turret.tick( dt )
	{
		this.bliptime += dt;
		this.bliptime %= 2;
		sprite_pos = this.position+vec3(0,0,0.575)+this.gun_dir*0.2;
		qdist = sqrt( ( sprite_pos - gSGR_Scene.camera.position ).length * 5 ) * 0.05;
		SS3D_UpdateParticleMesh( gSGR_SS3D, this.sprite_mesh, gSGR_Scene.camera.viewMatrix, vec3array(sprite_pos), floatarray(qdist), floatarray(5,0.5,0.3,1) );
		this.sprite_mesh_inst.enabled = ( this.bliptime < 1 || this.seetime > 0 ) && this.enabled;
		
		this.updateSeeTime( dt );
	}
	
	function Turret.draw_ui()
	{
	//	SS_DrawTextLine( "seetime: " $ this.seetime, gSGR_DefaultFont, 200, 200, color(1) );
	}
	
	return Turret;
}


function ENTITY_CREATE_save_trigger( params )
{
	SaveTrigger =
	{
		trig_pos = params.trigger_pos,
		trig_r = params.trigger_dist,
		ran = false,
	};
	
	function SaveTrigger.serialize()
	{
		return "save_trigger",
		{
			trigger_pos = this.trig_pos,
			trigger_dist = this.trig_r,
		},
		{
			ran = this.ran,
		};
	}
	
	function SaveTrigger.unserialize( data )
	{
		this.ran = data.ran;
	}
	
	function SaveTrigger.tick( dt )
	{
		if( !this.ran && ( PLAYER.position - this.trig_pos ).length < this.trig_r )
		{
			this.ran = true;
			SAVE_GAME();
		}
	}
	
	return SaveTrigger;
}


// CUTSCENE ENTITY
// PARAM=ACTION:
// --- common parameters ---
// > type (time, real)
// > from (time, real)
// > to (time, real)
// --- action types ---
// ~~ subtitle ~~
// > text (text to show, string)
// ~~ camera ~~
// > easing (easing function to use, string (function name postfix), default=smoothstep)
// > p0 (starting position, vec3)
// > p1 (ending position, vec3, null for =p0)
// > t0 (starting target, vec3)
// > t1 (ending target, vec3, null for =p1)
// > a0 (starting extension angle, float, null for 0)
// > a1 (ending extension angle, float, null for =a0)
// > d0 (starting extension distance, null for 0)
// > d1 (ending extension distance, float, null for =d0)
// PARAM=TRIGGER_POS,TRIGGER_DIST:
// - size/center of trigger sphere
// PARAM=LENGTH:
// - length in units of time of the cutscene
// PARAM=END:
// - whether cutscene ends the level
// PARAM=SAVE_END:
// - whether game should be saved at the end

function cutscene_easing_linear( x ){ return x; }
function cutscene_easing_smoothstep( x ){ return x * x * ( 3 - 2 * x ); }

function ENTITY_CREATE_cutscene( params )
{
	Cutscene =
	{
		actions = params.actions,
		trig_pos = params.trigger_pos,
		trig_r = params.trigger_dist,
		length = params.length,
		is_end = @params.end,
		save_end = @params.save_end,
		
		ran = false,
		t = 0.0,
		
		current_subtitle = null,
		subtitle_font = SS_CreateFont( "fonts/gauge-heavy.ttf", gSGR_Window.height / 30 ),
	};
	
	foreach( act : Cutscene.actions )
	{
		if( null === @act.from || null === @act.to || act.from == act.to )
			ERROR( "cutscene action - bad time values (can't match)" );
		if( act.type == "subtitle" )
		{
			if( !@act.text )
				ERROR( "no text for cutscene subtitle action" );
		}
		else if( act.type == "camera" )
		{
			if( typeof(@act.p0) != "vec3" || typeof(@act.t0) != "vec3" )
				ERROR( "invalid type for cutscene camera action" );
			if( typeof(@act.p1) != "vec3" ) act.p1 = act.p0;
			if( typeof(@act.t1) != "vec3" ) act.t1 = act.t0;
			act.a0 = toreal(@act.a0);
			act.d0 = toreal(@act.d0);
			if( @act.a1 === null ) act.a1 = toreal( act.a0 ); else act.a1 = toreal( act.a1 );
			if( @act.d1 === null ) act.d1 = toreal( act.d0 ); else act.d1 = toreal( act.d1 );
			if( !@act.easing )
				act.easing = "smoothstep";
			if( !@_G[ "cutscene_easing_" $ act.easing ] )
				ERROR( "invalid name of easing function '" $ act.easing $ "' - function (prefix=cutscene_easing_) not found" );
		}
	}
	
	function Cutscene.serialize()
	{
		return "cutscene",
		{
			actions = this.actions,
			trigger_pos = this.trig_pos,
			trigger_dist = this.trig_r,
			length = this.length,
			end = this.is_end,
			save_end = this.save_end,
		},
		{
			ran = this.ran,
			t = this.t,
		};
	}
	
	function Cutscene.unserialize( data )
	{
		this.ran = data.ran;
		this.t = data.t;
	}
	
	function Cutscene.runActions()
	{
		this.current_subtitle = null;
		foreach( act : this.actions )
		{
			if( act.from > this.t || act.to < this.t )
				continue;
			if( act.type == "subtitle" )
				this.current_subtitle = act.text;
			else if( act.type == "camera" )
			{
				q = ( this.t - act.from ) / ( act.to - act.from );
				easing_fn = _G[ "cutscene_easing_" $ act.easing ];
				q = easing_fn( q );
				
				pos = lerp( act.p0, act.p1, q );
				tgt = lerp( act.t0, act.t1, q );
				ang = lerp( act.a0, act.a1, q );
				dst = lerp( act.d0, act.d1, q );
				pos += vec3( cos(deg2rad(ang))*dst, sin(deg2rad(ang))*dst, 0 );
				
				gSGR_Scene.camera.position = pos;
				gSGR_Scene.camera.direction = ( tgt - pos ).normalized;
			}
		}
	}
	
	function Cutscene.tick( dt )
	{
		if( current_cutscene == this )
		{
			if( this.t > 1 )
			{
				foreach( k, v : gSGR_Keys )
				{
					if( k == SDLK_ESCAPE || k == SDLK_P )
						continue;
					if( v && !@gSGR_PrevKeys[ k ] )
					{
						this.t = this.length;
						break;
					}
				}
			}
			if( this.t >= this.length )
			{
				global current_cutscene = null;
				this.current_subtitle = null;
				if( this.is_end )
				{
					global mission_complete = true;
					Game.SetPaused( true );
					Game.OverlayScreens.push( PauseScreen );
				}
				else if( this.save_end )
				{
					SAVE_GAME();
				}
			}
			this.runActions();
			this.t += dt;
		}
		
		if( !this.ran && ( PLAYER.position - this.trig_pos ).length < this.trig_r )
		{
			this.ran = true;
			global current_cutscene = this;
		}
	}
	
	function Cutscene.draw_ui()
	{
		unit = gSGR_Window.height / 30;
		if( this.current_subtitle )
		{
			SS_DrawTextRect( this.current_subtitle, this.subtitle_font, color(1), DT_CENTER | DT_VCENTER,
				gSGR_Window.width / 4, gSGR_Window.width * 3 / 4, gSGR_Window.height * 3 / 5, gSGR_Window.height * 4 / 5 );
		}
	}
	
	return Cutscene;
}
