using UnityEngine; namespace Ironspore { public sealed class TrainingCourse : MonoBehaviour { static readonly Color Steel = new Color(.19f, .28f, .32f); static readonly Color Amber = new Color(.95f, .48f, .12f); static readonly Color Green = new Color(.16f, .92f, .65f); GameSession game; Renderer firstLamp, upperLamp; RangeTarget plate, movingPlate; Vector3 previousPosition; int gateCount, hits; float startedAt, finishSeconds; static readonly Vector3 StartGate = new Vector3(19, 0, -15); static readonly Vector3 UpperGate = new Vector3(19, 4.3f, 4); static readonly Vector3 Landing = new Vector3(19, 4.3f, 22); public string Instruction { get { Vector3 position = game.World.Player.transform.position; if (game.World.TransportReady) return GameLocalization.Text("range.board"); if (position.x < -16 && (position.z > 6 || Mathf.Abs(position.z + 8) < 4)) return GameLocalization.Text(game.Run.podUsed ? "range.recoverySent" : "range.recoveryGuide"); if (position.x < -7) return GameLocalization.Text("range.targetGuide", hits); if (gateCount == 3) return GameLocalization.Text("range.finish", finishSeconds); if (gateCount == 2) return GameLocalization.Text("range.brake"); if (gateCount == 1) return GameLocalization.Text("range.gate"); return GameLocalization.Text("range.choose"); } } public void Build(GameSession session) { game = session; previousPosition = game.World.Player.transform.position; firstLamp = Gate(StartGate); upperLamp = Gate(UpperGate); WorldFactory.Label("range.start", StartGate + Vector3.up * 5.95f, Green, transform); WorldFactory.Label("range.upper", UpperGate + Vector3.up * 5.95f, Green, transform); WorldFactory.Label("range.landing", Landing + Vector3.up * 1.7f, Green, transform); for (int i = -1; i <= 1; i++) WorldFactory.Box("LandingStripe", Landing + new Vector3(0,.02f,i * 1.35f), new Vector3(4.7f,.035f,.17f), Amber, transform, false); plate = Target(new Vector3(-13, 0, -2), false); movingPlate = Target(new Vector3(-25, 0, -2), true); WorldFactory.Box("TraverseRail", new Vector3(-25,.12f,-2), new Vector3(9,.24f,.8f), Steel, transform); foreach (int side in new[] { -1, 1 }) WorldFactory.Box("RailStop", new Vector3(-25 + side * 4.5f,.4f,-2), new Vector3(.35f,.8f,1.3f), Amber, transform); WorldFactory.Box("FiringLine", new Vector3(-19,.025f,-15), new Vector3(12,.04f,.15f), Amber, transform, false); WorldFactory.Label("range.gallery", new Vector3(-19,4.6f,-2), Amber, transform); WorldFactory.Label("range.salvage", new Vector3(-24,4,14), Green, transform); var desk = WorldFactory.Box("RangeResetDesk", new Vector3(-12, .85f, -15), new Vector3(1.4f, 1.7f, .85f), Steel, transform); WorldFactory.Box("ResetSwitch", desk.transform.position + Vector3.up * .92f, new Vector3(.8f,.14f,.5f), Green, transform, false, true); var reset = desk.AddComponent(); reset.kind = 4; reset.course = this; WorldFactory.Label("range.reset", desk.transform.position + Vector3.up * 1.6f, Green, transform); } Renderer Gate(Vector3 at) { foreach (int side in new[] { -1, 1 }) { Vector3 foot = at + Vector3.right * side * 3.1f; WorldFactory.Box("GateFoot", foot + Vector3.up * .14f, new Vector3(.8f,.28f,1.2f), Steel, transform); WorldFactory.Box("GatePost", foot + Vector3.up * 2.65f, new Vector3(.35f,5.3f,.5f), Steel, transform); WorldFactory.Box("GateStripe", foot + Vector3.up * 2, new Vector3(.38f,.8f,.53f), Amber, transform, false); } return WorldFactory.Box("GateLamp", at + Vector3.up * 5.35f, new Vector3(6.6f,.26f,.45f), Amber, transform, false, true).GetComponent(); } RangeTarget Target(Vector3 at, bool moves) { var carriage = new GameObject(moves ? "TraversingTarget" : "FixedTarget").transform; carriage.SetParent(transform, false); carriage.position = at; var stand = WorldFactory.Box("TargetStand", at + Vector3.up * .55f, new Vector3(2.2f,1.1f,.85f), Steel, carriage); if (moves) stand.layer = 10; var pivot = new GameObject("RangeTarget").transform; pivot.SetParent(carriage, false); pivot.position = at + Vector3.up * 1.15f; var panel = WorldFactory.Box("StrikePlate", pivot.position + Vector3.up * 1.15f, new Vector3(2.1f,2.3f,.22f), Amber, pivot); var bull = WorldFactory.Primitive(PrimitiveType.Cylinder, "Bullseye", panel.transform.position + Vector3.back * .14f, new Vector3(1.25f,.025f,1.25f), Steel, pivot, false, false); bull.transform.rotation = Quaternion.Euler(90,0,0); var lamp = WorldFactory.Box("TargetLamp", bull.transform.position + Vector3.back * .04f, new Vector3(.52f,.52f,.05f), Amber, pivot, false, true); foreach (Transform part in pivot.GetComponentsInChildren()) part.gameObject.layer = 10; var target = pivot.gameObject.AddComponent(); target.Initialize(this, panel.GetComponent(), lamp.GetComponent(), moves); return target; } void Update() { if (!game.Pilot || game.Paused) return; Vector3 position = game.Pilot.transform.position; if ((gateCount == 0 || gateCount == 3) && Crossed(previousPosition, position, StartGate)) { gateCount = 1; startedAt = Time.time; SetLamp(firstLamp, true); SetLamp(upperLamp, false); game.Notify("range.gate"); } else if (gateCount == 1 && Crossed(previousPosition, position, UpperGate)) { gateCount = 2; SetLamp(upperLamp, true); game.Notify("range.brake"); } else if (gateCount == 2 && Mathf.Abs(position.x - Landing.x) < 2.65f && Mathf.Abs(position.z - Landing.z) < 2.4f && Mathf.Abs(position.y - Landing.y) < .6f && game.Pilot.Grounded && game.Pilot.HorizontalSpeed < 1) { gateCount = 3; finishSeconds = Time.time - startedAt; game.Notify("range.finish", finishSeconds); GameAudio.Cue(620,.2f); } previousPosition = position; } static bool Crossed(Vector3 from, Vector3 to, Vector3 gate) { if (from.z >= gate.z || to.z < gate.z) return false; Vector3 crossing = Vector3.Lerp(from, to, (gate.z - from.z) / (to.z - from.z)); return Mathf.Abs(crossing.x - gate.x) < 2.9f && crossing.y >= gate.y - .5f && crossing.y < gate.y + 5; } internal static void SetLamp(Renderer renderer, bool on) { renderer.sharedMaterial = WorldFactory.Material(on ? Green : Amber, true); } internal void Hit() { game.Notify("range.hit", ++hits); GameAudio.Cue(880, .12f); } public void ResetPractice() { hits = gateCount = 0; previousPosition = game.Pilot.transform.position; SetLamp(firstLamp, false); SetLamp(upperLamp, false); plate.ResetTarget(); movingPlate.ResetTarget(); game.Notify("range.ready"); } } public sealed class RangeTarget : MonoBehaviour { TrainingCourse course; Collider strike; Renderer lamp; bool down, moving; float travelTime, holdTime; Vector3 origin; internal void Initialize(TrainingCourse owner, Collider collider, Renderer indicator, bool moves) { course = owner; strike = collider; lamp = indicator; moving = moves; origin = transform.parent.localPosition; } public bool Hit() { if (down || holdTime > 0) return false; down = !moving; if (moving) holdTime = 1f; else strike.enabled = false; TrainingCourse.SetLamp(lamp, true); course.Hit(); return true; } void Update() { if (down) transform.localRotation = Quaternion.RotateTowards(transform.localRotation, Quaternion.Euler(87,0,0), 240 * Time.deltaTime); if (!moving) return; if (holdTime > 0) { holdTime -= Time.deltaTime; if (holdTime <= 0) TrainingCourse.SetLamp(lamp, false); return; } travelTime += Time.deltaTime; transform.parent.localPosition = origin + Vector3.right * (3.1f * Mathf.Sin(travelTime * .85f)); } internal void ResetTarget() { down = false; holdTime = travelTime = 0; transform.parent.localPosition = origin; strike.enabled = true; transform.localRotation = Quaternion.identity; TrainingCourse.SetLamp(lamp, false); } } public sealed class RangeReset : WorldInteractable { public TrainingCourse course; public override string Prompt => GameLocalization.Text("range.reset"); public override void Activate() => course.ResetPractice(); } }