You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Duality;
using Duality.Components;
using Duality.Components.Renderers;
using Duality.Components.Physics;
using Duality.Resources;
using Duality.Drawing;
namespace DualStickSpaceShooter
{
public class Player : Component, ICmpUpdatable
{
public const float RespawnDelay = 10000.0f;
public static bool IsAnyPlayerAlive
{
get { return Scene.Current.FindComponents<Player>().Any(p => p.Active && p.ControlObject != null && p.ControlObject.Active); }
}
public static IEnumerable<Player> AllPlayers
{
get { return Scene.Current.FindComponents<Player>(); }
}
public static IEnumerable<Player> AlivePlayers
{
get { return Scene.Current.FindComponents<Player>().Where(p => p.Active && p.ControlObject != null && p.ControlObject.Active); }
}
private PlayerId id = PlayerId.Unknown;
private Ship controlObj = null;
private ColorRgba color = ColorRgba.White;
private float respawnTime = 0.0f;
private bool hasReachedGoal = false;
private float goalReachTime = 0.0f;
private ContentRef<Prefab> goalEffect = null;
private ContentRef<Sound> weaponSound = null;
private ContentRef<Sound> flightLoop = null;
[DontSerialize]
private InputMapping input = null;
[DontSerialize]
private GameObject goalEffectInstance = null;
public PlayerId Id
{
get { return this.id; }
set { this.id = value; }
}
public InputMethod InputMethod
{
get { return this.input != null ? this.input.Method : InputMethod.Unknown; }
set
{
if (this.input == null) this.input = new InputMapping();
this.input.Method = value;
}
}
public bool IsPlaying
{
get { return this.InputMethod != InputMethod.Unknown; }
}
public Ship ControlObject
{
get { return this.controlObj; }
set
{
this.controlObj = value;
if (this.controlObj != null)
this.controlObj.UpdatePlayerColor();
}
}
public ColorRgba Color
{
get { return this.color; }
set
{
this.color = value;
if (this.controlObj != null)
this.controlObj.UpdatePlayerColor();
}
}
public float RespawnTime
{
get { return this.respawnTime; }
}
public bool HasReachedGoal
{
get { return this.hasReachedGoal; }
}
public ContentRef<Prefab> GoalEffect
{
get { return this.goalEffect; }
set { this.goalEffect = value; }
}
public ContentRef<Sound> WeaponSound
{
get { return this.weaponSound; }
set { this.weaponSound = value; }
}
public ContentRef<Sound> FlightLoop
{
get { return this.flightLoop; }
set { this.flightLoop = value; }
}
public void NotifyGoalReached()
{
if (this.hasReachedGoal) return;
this.hasReachedGoal = true;
this.goalReachTime = (float)Time.GameTimer.TotalMilliseconds;
// Become a ghost
RigidBody body = this.controlObj.GameObj.GetComponent<RigidBody>();
body.CollidesWith = CollisionCategory.None;
// Become invincible
this.controlObj.Hitpoints = 10000.0f;
}
void ICmpUpdatable.OnUpdate()
{
// If the object we're controlling has been destroyed, forget about it
if (this.controlObj != null && this.controlObj.Disposed)
this.controlObj = null;
// See what player inputs there are to handle
if (this.input == null) this.input = new InputMapping();
bool hasInputMethod = this.input.Method != InputMethod.Unknown;
if (this.controlObj != null)
{
this.input.Update(this.controlObj.GameObj.Transform);
}
else
{
this.input.Update(null);
}
// Spawn the player object for the first time when input is detected
if (this.controlObj != null && this.input.Method != InputMethod.Unknown && !hasInputMethod)
{
// Find an alive player or determine that no one has started playing yet
bool gameAlreadyOver = false;
Player alivePlayer = Player.AlivePlayers.FirstOrDefault();
if (alivePlayer != null)
{
// Move near already alive player
Vector3 alivePlayerPos = alivePlayer.controlObj.GameObj.Transform.Pos;
this.controlObj.GameObj.Transform.Pos = alivePlayerPos;
}
else if (Player.AllPlayers.Any(p => p != this && p.IsPlaying))
{
// If we don't have an alive player, but do have playing players, the game must be already over.
gameAlreadyOver = true;
}
else
{
// Move near initial spawn point
this.controlObj.GameObj.Transform.Pos = SpawnPoint.SpawnPos;
}
// Spawn for the first time / enter the game
if (!gameAlreadyOver)
{
this.controlObj.GameObj.Active = true;
}
}
// Manage the object this player is supposed to control
if (this.controlObj != null)
{
if (this.hasReachedGoal)
{
RigidBody body = this.controlObj.GameObj.GetComponent<RigidBody>();
SpriteRenderer sprite = this.controlObj.GameObj.GetComponent<SpriteRenderer>();
// If we've reached the goal, update the final animation and do nothing else
float goalAnim = MathF.Clamp(((float)Time.GameTimer.TotalMilliseconds - this.goalReachTime) / 2000.0f, 0.0f, 1.0f);
// Don't move
body.LinearVelocity *= MathF.Pow(0.9f, Time.TimeMult);
this.controlObj.TargetAngleRatio = 0.1f;
this.controlObj.TargetThrust = Vector2.Zero;
// Fade out
if (sprite.CustomMaterial == null) sprite.CustomMaterial = new BatchInfo(sprite.SharedMaterial.Res);
sprite.CustomMaterial.Technique = DrawTechnique.SharpAlpha;
sprite.ColorTint = sprite.ColorTint.WithAlpha(1.0f - goalAnim);
// Spawn a goal reached effect
if (goalAnim > 0.2f && this.goalEffect != null && this.goalEffectInstance == null)
{
this.goalEffectInstance = this.goalEffect.Res.Instantiate(this.controlObj.GameObj.Transform.Pos);
Scene.Current.AddObject(this.goalEffectInstance);
}
// Let the ship disappear
if (goalAnim >= 1.0f)
{
this.controlObj.GameObj.Active = false;
}
}
else if (this.controlObj.Active)
{
// Apply control inputs to the controlled object
this.controlObj.TargetAngle = this.input.ControlLookAngle;
this.controlObj.TargetAngleRatio = this.input.ControlLookSpeed;
this.controlObj.TargetThrust = this.input.ControlMovement;
if (this.input.ControlFireWeapon)
this.controlObj.FireWeapon();
}
else if (hasInputMethod && Player.IsAnyPlayerAlive && !this.hasReachedGoal)
{
// Respawn when possible
this.respawnTime += Time.MsPFMult * Time.TimeMult;
if (this.respawnTime > RespawnDelay)
{
// Move near alive player
Player alivePlayer = Player.AlivePlayers.FirstOrDefault();
Vector3 alivePlayerPos = alivePlayer.controlObj.GameObj.Transform.Pos;
this.controlObj.GameObj.Transform.Pos = alivePlayerPos;
// Respawn
this.respawnTime = 0.0f;
this.controlObj.Revive();
}
}
}
// Quit the game when requested
if (this.input.ControlQuit)
DualityApp.Terminate();
// If it's game over, allow to restart the game
GameOverScreen gameOverScreen = Scene.Current.FindComponent<GameOverScreen>();
if (gameOverScreen != null && gameOverScreen.HasGameEnded)
{
if (this.input.ControlStart)
Scene.Reload();
}
}
}
}