Code:
//
//Various emergency & service cars script created by posters at http://forum.1cpublishing.eu/showthread.php?t=26112 and by naryv
//Hacked extensively by flug
//$reference parts/core/Strategy.dll
//$reference parts/core/gamePlay.dll
//$reference System.Core.dll
using System;
//using System.Core;
using System.Collections;
using maddox.game;
using maddox.game.world;
using maddox.GP;
using part;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;
public class Mission : AMission
{
public bool DEBUG=false;
public int VEHICLE_LIFE_SEC = 120;
public double CAR_POS_RADIUS = 80; //distance vehicles will be positioned from the center point of the Birthplace, Airfield, etc where cars are positioned
public int TICKS_PER_MINUTE=1986; //empirically, based on a timeout test. This is approximate & varies slightly.
//for landing or crash, they start SPAWN_START_DISTANCE_M away from the plane in the direction of the nearest BirthPlace or Airport point. They end SPAWN_END_DISTANCE_M away from the a/c. (Distances are approx., various randomness & functions added on top of these values.)
public int SPAWN_START_DISTANCE_M = 100; //how far away they start from the a/c when approaching after landing
public int SPAWN_END_DISTANCE_M = 20; //how close they approach you upon (eg) landing
//For spawn-in, the vehicles start this close to the a/c and proceed to
// the birthplace or airport point.
public int SPAWN_START_DISTANCE_REVERSE_M = 12; //how close they start when spawning in onPlaceEnter
private List<AiActor> actorPlaceEnterList; //HashSet doesn't work for some reason; it would be a better solution
private List<AiAircraft> aircraftDamagedList;
private List<AiAircraft> aircraftActiveList;
maddox.game.ABattle Battle;
public Mission () {
//HashSet<int> evenNumbers = new HashSet<int>();
actorPlaceEnterList = new List<AiActor>();
aircraftDamagedList = new List<AiAircraft>();
aircraftActiveList = new List<AiAircraft>();
}
//Listen to events of every mission
public override void Init(maddox.game.ABattle battle, int missionNumber)
{
base.Init(battle, missionNumber);
Battle=battle;
MissionNumberListener = -1; //Listen to events of every mission
//This is what allows you to catch all the OnTookOff, OnAircraftDamaged, and other similar events. Vitally important to make this mission/cs file work!
//If we load missions as sub-missions, as we often do, it is vital to have this in Init, not in "onbattlestarted" or some other place where it may never be detected or triggered if this sub-mission isn't loaded at the very start.
if (DEBUG) GamePlay.gpLogServer(null, ".Net framework: " + Environment.Version, new object[] { });
}
public override void OnBattleStarted()
{
base.OnBattleStarted();
//MissionNumberListener = -1;
}
Random rnd = new Random();
[Flags]
internal enum ServiceType // ??? ????????????? ???????
//note that all these types don't work; the actual type is determined by
//createemrgcarmission depending on a/c type, army, and a few other things.
//So it's not really determined by the settings in the curTechCar.CarType field
{
NONE = 0,
EMERGENCY = 1,
FIRE = 2,
FUEL = 4,
AMMO = 8,
BOMBS = 16,
PRISONERCAPTURE = 32,
SPAWNIN = 64
}
//Like AiActor or AiBirthplace but only has .Name() & .Loc()
internal class BasePos
{
internal string _Name;
internal Point3d _Pos;
public BasePos (string name, Point3d pos)
{
if (name!=null)
this._Name=name;
else this._Name="";
//if (pos!=null)
this._Pos=pos;
//else this.Pos=new Point3d(0,0,0);
}
public BasePos (BasePos bp)
{
this._Name=bp.Name();
this._Pos=bp.Pos();
Console.WriteLine ("BasePos inited: " + this.ToString ("F2"));
}
//a default constructor . . .
public BasePos (object o=null)
{
this._Name="";
this._Pos=new Point3d(0,0,0);
}
public string Name(string name=null) {
if (name==null) return this._Name;
else {
this._Name=name;
return null;
}
}
public Point3d Pos(Point3d pos) {
this._Pos=pos;
return this._Pos;
}
public Point3d Pos() {
return this._Pos;
}
public string ToString (string format){
return _Name + " "
+ _Pos.x.ToString( format ) + " "
+ _Pos.y.ToString( format ) + " "
+ _Pos.z.ToString( format );
}
}
internal class TechCars
{
internal AiGroundGroup TechCar { get; set; }
internal BasePos basePos { get; set; }
internal IRecalcPathParams cur_rp { get; set; }
internal int RouteFlag = 0;
internal int cartype = 0;
internal int servPlaneNum = -1;
internal int MAX_CARS = 100;
internal ServiceType CarType { get { return (ServiceType)cartype; } set { cartype = (int)value; } }
public TechCars(AiGroundGroup car, BasePos airoport, IRecalcPathParams rp)
{
this.TechCar = car;
this.basePos = airoport;
this.cur_rp = rp;
Console.WriteLine("TechCars created. basePos: " + this.basePos.ToString("F2"));
}
}
internal class PlanesQueue {
internal AiAircraft aircraft { get; set; }
internal BasePos basePos { get; set; }
internal int state = 0;
internal ServiceType State { get { return (ServiceType)state; } set { state = (int)value; } }
internal int Lifetime = 0;
internal float health = 1;
public PlanesQueue(AiAircraft aircraft, BasePos basePos, int state)
{
this.aircraft = aircraft;
this.basePos = basePos as BasePos;
this.state = state;
}
}
internal List<TechCars> CurTechCars = new List<TechCars>();
internal List<PlanesQueue> CurPlanesQueue = new List<PlanesQueue>();
TechCars TmpCar = null;
bool MissionLoading = false;
int MissionLoadingAircraftNumber = -1;
internal double PseudoRnd(double MinValue, double MaxValue)
{
return rnd.NextDouble() * (MaxValue - MinValue) + MinValue;
}
public override void OnActorTaskCompleted(int missionNumber, string shortName, AiActor actor)
{
base.OnActorTaskCompleted(missionNumber, shortName, actor);
if (DEBUG) GamePlay.gpLogServer(null, "OnActorTaskComplete", new object[] { });
AiActor ai_actor = actor as AiActor;
if (ai_actor != null)
{
if (ai_actor is AiGroundGroup)
for (int i = 0; i < CurTechCars.Count; i++) // ???? ????????????? ??????? ??????? ?? ?????????????? ????????, ????????? ?? ????????????
{
if (CurTechCars[i].TechCar == ai_actor as AiGroundGroup) {
//if (CurTechCars[i].RouteFlag == 1)
TechCars car = CurTechCars[i] as TechCars;
if (DEBUG) GamePlay.gpLogServer(null, "OnActorTaskComplete - ending plane service for " + i.ToString() + " in 120 sec.", new object[] { });
//this is basically to ensure that AI objects don't just hang around indefinitely when their tasks are done.
//In normal behavior, they may complete several tasks in the course of moving abou the airport, so we don't just want
//to destroy them immediately when task is done
Timeout(VEHICLE_LIFE_SEC, () =>
{
if (DEBUG) GamePlay.gpLogServer(null, "OnActorTaskComplete - ending plane service for " + i.ToString() + " now", new object[] { });
EndPlaneService(car, ai_actor as AiGroundGroup);
});
}
//we're just destroying them @ this point
// else
// CheckNotServicedPlanes(i);
};
}
}
internal void CheckNotServicedPlanes(int techCarIndex)
{
for (int j = 0; j < CurPlanesQueue.Count; j++)
{
if (CurTechCars[techCarIndex].TechCar.IsAlive() && (CurPlanesQueue[j].basePos == CurTechCars[techCarIndex].basePos) && ((CurTechCars[techCarIndex].CarType & CurPlanesQueue[j].State) != 0) && (CurTechCars[techCarIndex].servPlaneNum == -1))
{
if (SetEmrgCarRoute(j, techCarIndex)) // ?????????? ??????? ??????????? ????????? ???????
{
return;
}
}
}
}
//Removes the ground vehicle from the CurTechCars list & also destroys the AI object
//We call it with the List item (not the index) because the index can change between call & execution, esp. if call via a timeout, which is common
//We also include the TechCar field, (an AiGroundGroup) because sometimes the List item can be destroyed but the actual Ai Airgroup is still floating around undead
//
internal void EndPlaneService(TechCars tC, AiGroundGroup ground=null)
{
try
{
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning now", new object[] { });
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning " + tC.servPlaneNum.ToString(), new object[] { });
if (tC != null) {
if (DEBUG) GamePlay.gpLogServer(null, " Number of objects: " + tC.TechCar.GetItems().Length, new object[] { });
//if (CurTechCars[techCarIndex].cur_rp == null) return;
tC.cur_rp = null; // ?????????? ???????
//Just destroy the ground items at this point.
if (tC.TechCar.GetItems() != null && tC.TechCar.GetItems().Length > 0)
{
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning 1 ", new object[] { });
foreach (AiActor actor in tC.TechCar.GetItems())
{
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning 2 " , new object[] { });
(actor as AiGroundActor).Destroy();
}
}
CurTechCars.Remove(tC);
}
if (ground != null) {
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning 3 ", new object[] { });
foreach (AiActor actor in ground.GetItems())
{
if (DEBUG) GamePlay.gpLogServer(null, "EndPlaneService/despawning 4 " , new object[] { });
(actor as AiGroundActor).Destroy();
}
}
}
catch (Exception e) {System.Console.WriteLine (e.ToString());}
}
internal bool MoveFromRWay(int carNum)
{
bool result = false;
if (DEBUG) GamePlay.gpLogServer(null, "Removing aircraft from runway at " + CurTechCars[carNum].basePos.Name(), new object[] { });
if ((GamePlay.gpLandType(CurTechCars[carNum].TechCar.Pos().x, CurTechCars[carNum].TechCar.Pos().y) & LandTypes.ROAD) == 0)
return result;
Point3d TmpPos = CurTechCars[carNum].TechCar.Pos();
while (((GamePlay.gpLandType(TmpPos.x, TmpPos.y) & LandTypes.ROAD) != 0))
{
TmpPos.x += 10f;
TmpPos.y += 10f;
};
Point2d EmgCarStart, EmgCarFinish;
EmgCarStart.x = CurTechCars[carNum].TechCar.Pos().x; EmgCarStart.y = CurTechCars[carNum].TechCar.Pos().y;
EmgCarFinish.x = TmpPos.x; EmgCarFinish.y = TmpPos.y;
CurTechCars[carNum].servPlaneNum = -1;
CurTechCars[carNum].RouteFlag = 0;
CurTechCars[carNum].cur_rp = null;
CurTechCars[carNum].cur_rp = GamePlay.gpFindPath(EmgCarStart, 10f, EmgCarFinish, 10f, PathType.GROUND, CurTechCars[carNum].TechCar.Army());
result = true;
return result;
}
public bool SetEmrgCarRoute(int aircraftNumber,int carNum)
{
bool result = false;
if (DEBUG) GamePlay.gpLogServer(null, "Setting a Car Route "+aircraftNumber.ToString() + " " + carNum.ToString() + " at " + CurTechCars[carNum].basePos.Name() , new object[] { });
if (CurTechCars[carNum].TechCar != null)
{
CurTechCars[carNum].servPlaneNum = aircraftNumber; // ????????????? ????? ?????????????? ????????
if (CurTechCars[carNum].cur_rp == null)
{
Point2d EmgCarStart, EmgCarFinish, LandedPos;
LandedPos.x = CurPlanesQueue[aircraftNumber].aircraft.Pos().x; LandedPos.y = CurPlanesQueue[aircraftNumber].aircraft.Pos().y;
int Sign = ((carNum % 2) == 0) ? 2 : -2;
EmgCarStart.x = CurTechCars[carNum].TechCar.Pos().x; EmgCarStart.y = CurTechCars[carNum].TechCar.Pos().y;
//Drive the car from where it is to the point located in the direction of the aircraft position but 20 meters short of it.
double disx, disy;
disx=Math.Abs(EmgCarStart.x - LandedPos.x) - 20;
if (disx<10) disx=20;
disy=Math.Abs(EmgCarStart.y - EmgCarStart.y) - 20;
if (disy<10) disy=20;
EmgCarFinish.x = EmgCarStart.x - disx * ((EmgCarStart.x - LandedPos.x) / Math.Abs(EmgCarStart.x - LandedPos.x)); EmgCarFinish.y = EmgCarStart.x - disy * ((EmgCarStart.y - LandedPos.y) / Math.Abs(EmgCarStart.y - LandedPos.y));
//EmgCarFinish.x = LandedPos.x - PseudoRnd(0f, 1f) * ((LandedPos.x - EmgCarStart.x) / (Math.Abs(LandedPos.x - EmgCarStart.x))) - Sign;
//EmgCarFinish.y = LandedPos.y - PseudoRnd(0f, 1f) * ((LandedPos.y - EmgCarStart.y) / (Math.Abs(LandedPos.y - EmgCarStart.y))) - Sign;
//For spawn-in, we want the cars to start in close to the a/c & drive away
CurTechCars[carNum].cur_rp = GamePlay.gpFindPath(EmgCarStart, 15f, EmgCarFinish, 15f, PathType.GROUND, CurTechCars[carNum].TechCar.Army());
if (DEBUG) GamePlay.gpLogServer(null, "Setting a Car Route "+aircraftNumber.ToString() + " " + carNum.ToString() + " " + EmgCarStart.ToString() + " to " + EmgCarFinish.ToString() + " at " + CurTechCars[carNum].basePos.Name() , new object[] { });
result = true;
}
}
return result;
}
public override void OnMissionLoaded(int missionNumber)
{
base.OnMissionLoaded(missionNumber);
if (missionNumber > 0) //whenever a new mission loads, this slurps up any matching groundcars into the curTechCars list so they can be manipulated etc
//if (missionNumber==MissionNumber ) // important check!
{
if (DEBUG) GamePlay.gpLogServer(null, "Starting vehicle sub-mission loaded", new object[] { });
List<string> CarTypes = new List<string>();
CarTypes.Add(":0_Chief_Emrg_");
CarTypes.Add(":0_Chief_Fire_");
CarTypes.Add(":0_Chief_Fuel_");
CarTypes.Add(":0_Chief_Ammo_");
CarTypes.Add(":0_Chief_Bomb_");
CarTypes.Add(":0_Chief_Prisoner_");
AiGroundGroup MyCar = null;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < CarTypes.Count; j++)
{
MyCar = GamePlay.gpActorByName(missionNumber.ToString() + CarTypes[j] + i.ToString()) as AiGroundGroup;
//if (DEBUG) GamePlay.gpLogServer(null, "Creating groundcar group for " +missionNumber.ToString() + CarTypes[j] + i.ToString(), new object[] { });
if (MyCar != null)
{
TmpCar = new TechCars(MyCar, FindNearestAirport(MyCar), null);
if (DEBUG) GamePlay.gpLogServer(null, "Creating groundcar group at " + FindNearestAirport(MyCar).Name() + " " + MissionLoadingAircraftNumber.ToString(), new object[] { });
TmpCar.CarType = (ServiceType)(1 << j);
TmpCar.cur_rp = null;
TmpCar.servPlaneNum=MissionLoadingAircraftNumber;
if (!CurTechCars.Contains(TmpCar))
CurTechCars.Add(TmpCar);
//if (CurTechCars.count < MAX_CARS) CurTechCars.Add(TmpCar);
//These things are unruly, so we're setting a max life on them.
Timeout(2*VEHICLE_LIFE_SEC, () =>
{
(MyCar as AiGroundActor).Destroy();
});
MissionLoading = false;
};
}
}
}
}
public override void OnTickGame() {
base.OnTickGame();
//try {
if ( (Time.tickCounter()) == 0) {
// if (DEBUG) GamePlay.gpLogServer(null, "Ground vehicles started ", new object[] { });
}
if ((Time.tickCounter() % (TICKS_PER_MINUTE/6)) == 12 ) {
checkForStoppedAircraft(aircraftActiveList);
}
if (Time.tickCounter() % 64 == 0)
{
//if (DEBUG) GamePlay.gpLogServer(null, "Ground vehicles continues . . . ", new object[] { });
for (int i = 0; i < CurPlanesQueue.Count; i++)
{
CurPlanesQueue[i].Lifetime++;
if (DEBUG) GamePlay.gpLogServer(null, "Lifetime: " + CurPlanesQueue[i].Lifetime, new object[] { });
if ((CurPlanesQueue[i].State == ServiceType.NONE) || (CurPlanesQueue[i].aircraft == null) || (CurPlanesQueue[i].Lifetime > (int)((double)VEHICLE_LIFE_SEC*TICKS_PER_MINUTE/64/60)))
{
foreach ( TechCars car in CurTechCars ) //don't use a for count/index loop here as we are destroying some of the objects mid-loop . . . arghh
{
if (car.servPlaneNum == i) {
if (DEBUG) GamePlay.gpLogServer(null, "Removing ground car for plane " + car.servPlaneNum + " " + car.CarType + " in 5 seconds", new object[] { });
//EndPlaneService(j);
Timeout ( 5f, () => { EndPlaneService(car, car.TechCar); });
}
}
CurPlanesQueue.RemoveAt(i);
}
};
foreach ( TechCars car in CurTechCars ) // (int i = 0; i < CurTechCars.Count; i++) //again ix-nay on the loop-for-ay . . . .
{
if (DEBUG) GamePlay.gpLogServer(null, "Ground car at " + car.basePos.Name(), new object[] { });
//TechCars car = CurTechCars[i];
if ((car.TechCar != null && car.cur_rp != null) && (car.cur_rp.State == RecalcPathState.SUCCESS) )
{
if (car.TechCar.IsAlive()) // && (car.RouteFlag == 0)) // && (car.servPlaneNum != -1))
{
car.RouteFlag = 1;
car.cur_rp.Path[0].P.x = car.TechCar.Pos().x; car.cur_rp.Path[0].P.y = car.TechCar.Pos().y;
car.TechCar.SetWay(car.cur_rp.Path);
//if (car.servPlaneNum != -1) car.RouteFlag = 0;
}
//The code below avoids the current plane, right? But, I'm worried about these ground vehicles hitting **all the other planes** that might be about this particular airport . . . . maybe some fixup needed
double Dist = Math.Sqrt((car.cur_rp.Path[car.cur_rp.Path.Length - 1].P.x - car.TechCar.Pos().x) * (car.cur_rp.Path[car.cur_rp.Path.Length - 1].P.x - car.TechCar.Pos().x) + (car.cur_rp.Path[car.cur_rp.Path.Length - 1].P.y - car.TechCar.Pos().y) * (car.cur_rp.Path[car.cur_rp.Path.Length - 1].P.y - car.TechCar.Pos().y));
if (car.servPlaneNum != -1)
{
if (Dist < ((CurPlanesQueue[car.servPlaneNum].aircraft.Type() == AircraftType.Bomber) ? 20f : 10f))
//EndPlaneService(i);
EndPlaneService(car, car.TechCar);
}
else if (Dist < 15f)
{
//EndPlaneService(i);
EndPlaneService(car, car.TechCar);
}
}
if ((car.cur_rp == null) && (car.RouteFlag == 0) && (car.servPlaneNum != -1))
{
//EndPlaneService(car, car.TechCar);
};
if (car.servPlaneNum == -1 || car.TechCar == null)
//EndPlaneService(i); //Once it is no longer serving a plane, we just zap it.
EndPlaneService(car, car.TechCar);
};
}
//} catch (Exception e) {System.Console.WriteLine (e.ToString());}
}
internal BasePos FindNearestAirport(AiActor actor)
{
try
{
if (actor==null) return null;
Point3d pd = actor.Pos();
if (DEBUG) GamePlay.gpLogServer(null, "Checking airport " + actor.Name(), new object[] { });
return FindNearestAirport(pd) as BasePos;
}
catch (Exception e) {System.Console.WriteLine (e.ToString()); BasePos ret3=null; return ret3; }
}
internal BasePos FindNearestAirport(Point3d pd)
{
try{
AiActor aMin = null;
AiBirthPlace aMinB = null;
double d2Min = 0;
BasePos ret= new BasePos ();
BasePos ret2=new BasePos ();
Point3d retpd;
//If we find a birthplace (ie, spawnpoint) closer than 2km we return that
//otherwise we'll search all airports for something closer
//And . . AiBirthPlace & AiAirport & AiActor are ALMOST the same thing but then again not quite so we have to dance a bit.
aMinB=FindNearestBirthplace(pd);
//if (DEBUG) GamePlay.gpLogServer(null, "Checking airport (Birthplace) found " + aMinB.Name() + " " + aMinB.Pos().distance(ref pd).ToString("F0"), new object[] { });
//if (1==0 && aMinB!= null) {
if (aMinB!= null) {
d2Min=aMinB.Pos().distance(ref pd);
//if (DEBUG) GamePlay.gpLogServer(null, "Checking airport (Birthplace) found " + aMinB.Name() + " " + aMinB.Pos().distance(ref pd).ToString("F0")
//+ " " + aMinB.Pos().ToString(), new object[] { });
if (d2Min<2000) {
retpd=aMinB.Pos();
if (retpd.z==0) retpd.z = pd.z; //BirthPlaces usu. have elevation 0 which makes the ai route finder die horribly
ret= new BasePos (aMinB.Name(), retpd);
return ret;
}
}
if (DEBUG) GamePlay.gpLogServer(null, "Checking airport (Birthplace) NOfound " + d2Min.ToString("F0"), new object[] { });
int n = GamePlay.gpAirports().Length;
for (int i = 0; i < n; i++)
{
AiActor a = (AiActor)GamePlay.gpAirports()[i];
if (a==null) continue;
if (!a.IsAlive()) continue;
//if (DEBUG) GamePlay.gpLogServer(null, "Checking airport " + a.Name(), new object[] { });
Point3d pp;
pp = a.Pos();
pd.z = pp.z;
double d2 = pd.distanceSquared(ref pp);
if ((aMin == null) || (d2 < d2Min) )
{
aMin = a;
d2Min = d2;
}
}
if (DEBUG) GamePlay.gpLogServer(null, "CAirport Found: " + aMin.Name() + " " + aMin.Pos().ToString() + " dist " + d2Min.ToString("F2"), new object[] { });
//Hmm, with our new scheme it doesn't really matter if aMin is very
//distant or what. The cars always start relatively close to the a/c
//and **in the direction of** the airport, but not *at* the airport
//if (d2Min > 2250000.0)
// aMin = null;
//return aMin as BasePos
if (aMin != null) ret2= new BasePos (aMin.Name(), aMin.Pos());
if (DEBUG) GamePlay.gpLogServer(null, "CAirport Returning: " + ret2.Name() + " " + ret2.Pos().ToString() + " dist " + d2Min.ToString("F2") + " " + ret2.ToString("F0"), new object[] { });
return ret2;
}
catch (Exception e) {System.Console.WriteLine (e.ToString()); BasePos ret3=null; return ret3; }
}
public AiBirthPlace GetBirthPlaceByName(string birthPlaceName)
{
foreach (AiBirthPlace bp in GamePlay.gpBirthPlaces())
{
if (DEBUG) GamePlay.gpLogServer(null, "Checking airport " + bp.Name(), new object[] { });
if (bp.Name() == birthPlaceName)
return bp;
}
return null;
}
public AiBirthPlace FindNearestBirthplace(AiActor actor)
{
//AiBirthPlace nearestBirthplace = null;
//AiBirthPlace[] birthPlaces = GamePlay.gpBirthPlaces();
Point3d pos = actor.Pos();
return FindNearestBirthplace(pos);
}
public AiBirthPlace FindNearestBirthplace (Point3d pos)
{
AiBirthPlace nearestBirthplace = null;
AiBirthPlace[] birthPlaces = GamePlay.gpBirthPlaces();
if (birthPlaces != null)
{
foreach (AiBirthPlace airport in birthPlaces)
{
if (nearestBirthplace != null)
{
//if (DEBUG) GamePlay.gpLogServer(null, "Checking airport " + airport.Name() + " "
// + airport.Pos().distance(ref pos).ToString("F0"), new object[] { });
if (nearestBirthplace.Pos().distance(ref pos) > airport.Pos().distance(ref pos))
nearestBirthplace = airport;
}
else nearestBirthplace = airport;
}
}
//AiActor ret=new AiActor();
//ret.Pos( nearestBirthplace.Pos());
//ret.Name(nearestBirthplace.Name());
if (DEBUG) GamePlay.gpLogServer(null, "Checking airport FOUND" + nearestBirthplace.Name() + " "
+ nearestBirthplace.Pos().distance(ref pos).ToString("F0"), new object[] { });
return nearestBirthplace;
}
//CONTINUED IN PART 2!
Note that I trimmed down some comments, debugging code, etc to make it fit on the forum better. Full code is in the