////////////////////////////////////////////////////////////////////////////////
// Vehicle.cpp
// Base class for vehicles
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

#include "AeroTech.h"
#include "BGCPString.h"
#include "BipedMech.h"
#include "Config.h"
#include "FileList.h"
#include "GameObject.h"
#include "CombatVehicle.h"
#include "intersections.h"
#include "MechSim.h"
#include "MWBase.h"
#include "QuadMech.h"
#include "Shot.h"
#include "Vehicles.h"
#include "VTOL.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_VEHICLES_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

Vehicle::Vehicle(const char* className, BGClassID classID, RenderableObject* parent, int limbCount, const char* armorDiagramSubtitle)
: GameObject(className, classID, parent),
  tonnage(0),
  jumpJets(0),
  heatSinks(10),
  engineRating(0),
  mHeading(0.0),
  mJumpJetsActive(0),
  mVehicleTexture(nullptr),
  mActiveWeapon(0),
  p_front (Point3D(0.0, 0.0, 1.0)),
  p_left (Point3D(-1.0, 0.0, 0.0)),
  p_up (Point3D(0.0, 1.0, 0.0)),
  mArmorDiagramSubtitle(armorDiagramSubtitle),
  mSalvage(false),
  cockpitCoords (0.0, 9.0, 0.0),
  mUseCockpitDisplays(false),
  preferLowerArms(false),
  vehicleStatus(shutDown),
  mAI(nullptr),
  mHasGroundContact(false),
  mEngineHits(0),
  mHeatSinkCriticalHits(0),
  mMobility(1.0),
  mMetersPerSecond(0.0),
  mSpeed(0.0),
  mMaxTurnPerSecond(90.0),
  m_turnDelta(0.0),
  m_speedDelta(0.0)
{
	SET_SCOPE;

	// Add nodes for the limbs (RenderableObject adds itself to its parent)
	while ((int) getObjectCount() < limbCount) {
		Mesh* mesh = new Mesh("Mesh", MeshID, this);
	}

	m_joint.resize(limbCount);

	altimeterDisplayCoords = DisplayComponentCoords(0.0, 0.0, 0.0, 0.0);
	jumpJetDisplayCoords = DisplayComponentCoords(0.0, 0.0, 0.0, 0.0);
	artificialHorizonDisplayCoords = DisplayComponentCoords(0.0, 0.0, 0.0, 0.0);

	RETURN;
}

// Destructor
Vehicle::~Vehicle()
{
	SET_SCOPE;
	for (size_t i = 0; i < mThingsToDeleteInDestructor.size(); ++i) {
		safeDelete(mThingsToDeleteInDestructor[i]);
	}
	mThingsToDeleteInDestructor.clear();
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Check for items that the user may not add in the MechLab

bool Vehicle::userMayAddDirectly (int section, WeaponType weaponNo)
{
	SET_SCOPE;
	if (weaponNo == WeaponType::CE_Jump_Jet
	||  weaponNo == WeaponType::Unused_Critical_Slot
	||  weaponNo == WeaponType::CE_Engine
	||  weaponNo == WeaponType::CE_Gyro
	||  weaponNo == WeaponType::CE_Shoulder
	||  weaponNo == WeaponType::CE_Upper_Arm_Actuator
	||  weaponNo == WeaponType::CE_Hip
	||  weaponNo == WeaponType::CE_Upper_Leg_Actuator
	||  weaponNo == WeaponType::CE_Lower_Leg_Actuator
	||  weaponNo == WeaponType::CE_Foot)
	{
		RETURN false;
	} else {
		RETURN true;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Check if a critical may be removed by the user

bool Vehicle::userMayRemoveDirectly (WeaponType weaponNo)
{
	if (weaponNo == WeaponType::Unused_Critical_Slot
	||  weaponNo == WeaponType::CE_Engine
	||  weaponNo == WeaponType::CE_Cockpit
	||  weaponNo == WeaponType::CE_Gyro
	||  weaponNo == WeaponType::CE_Life_Support
	||  weaponNo == WeaponType::CE_Cockpit
	||  weaponNo == WeaponType::CE_Sensors
	||  weaponNo == WeaponType::CE_Shoulder
	||  weaponNo == WeaponType::CE_Upper_Arm_Actuator)
	{
		return false;
	} else {
		return true;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Remove all equipment criticals of given type
// Useful for changing the type of heat sinks, internal structure, armor

int Vehicle::removeAll(WeaponType eqptType)
{
	int removed = 0;

	SET_SCOPE;
	for (int i = 0; i < getSectionCount(); ++i) {
		SET_OP;
		int crits = countWeaponCriticals(i, eqptType);
		if (crits > 0) {
			SET_OP;
			if (removeWeapon(i, 0, eqptType, crits)) {
				++removed;
			}
		}
	}

	RETURN removed;
}

////////////////////////////////////////////////////////////////////////////////
// Obtain mech information

int Vehicle::getInteger(int info) const
{
	SET_SCOPE;
	switch (info) {
	case Info_Tonnage: RETURN tonnage;
	case Info_EngineRating: RETURN engineRating;
	case Info_EngineType: RETURN EngineType;
	case Info_JumpJets: RETURN jumpJets;
	case Info_WalkingHexes: RETURN engineRating / tonnage;
	case Info_RunningHexes: RETURN ((int) ((engineRating / tonnage + 0.5)*1.5));
	case Info_Heat_Sinks: RETURN heatSinks;
	case Info_Heat_Sink_Type: RETURN HeatSinkType;
	case Info_Armor_Type: RETURN ArmorType;
	case Info_TechType: RETURN techType;
	}

	RETURN 0;
}

bool Vehicle::setInteger (int info, int newVal)
{
	SET_SCOPE;
	switch (info) {
	case Info_Tonnage: 
		tonnage = newVal;
		break;
	case Info_EngineRating: 
		engineRating = newVal;
		break;
	case Info_EngineType: 
		EngineType = newVal;
		break;
	case Info_JumpJets: 
		jumpJets = newVal;
		break;
	case Info_Heat_Sinks: 
		heatSinks = newVal;
		break;
	case Info_Armor_Type: 
		ArmorType = newVal;
		break;
	default:
		return false;
	}

	calcRealWeight();
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////

bool Vehicle::loadConfig (const PathChar* cfgFN)
{
	SET_SCOPE;
	if (bgstrlen(cfgFN) == 0) {
		RETURN false;
	}

	SET_OP;
	MemoryBlock mekFile;
	if (mekFile.loadFromFile(cfgFN)) {
		SET_OP;
		loadFromMW2MEK(&mekFile);
		mJumpJetFuel = jumpJets;
		calcRealWeight();
		RETURN true;
	} else {
		RETURN false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Count number of criticals belonging to a weapon type in a section

int Vehicle::countWeaponCriticals (int currSection, WeaponType weaponNo) const
{
	SET_SCOPE;
	int i = 0;
	int found = 0;
	int max = getCriticalCount(currSection);

	while (i < max) {
		SET_OP;
		int w = getCriticals(currSection, i);
		if (w == weaponNo) {
			++found;
		} 
		++i;
	}

	RETURN found;
}

int Vehicle::countWeaponInAllSections(WeaponType weaponNo) const
{
	SET_SCOPE;
	int count = 0;

	for (int i = 0; i < getSectionCount(); ++i) {
		count += countWeaponCriticals(i, weaponNo);
	}

	RETURN count;
}

////////////////////////////////////////////////////////////////////////////////

int Vehicle::calcMASCTonsAndSlots() const
{
	if (techType == Tech_Type_Clan) {
		return (int) ceil(tonnage / 25.0);
	} else {
		return (int) ceil(tonnage / 20.0);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Calculate weight of armor
// Refer to Tech Manual, page 54

double Vehicle::calcArmorWeight() const
{
	SET_SCOPE;
	int armorPoints = countArmorPoints();
	if (armorPoints <= 0) {
		RETURN 0.0;
	}

	if (ArmorType == ARMOR_TYPE_STD) {
		double result = armorPoints / 16.0;
		RETURN result;
	} else {
		double factor = 1.2;
		if (techType != Tech_Type_Clan) {
			factor = 1.12;
		}

		double result = 0.0;
		while ((int)(result * 16 * factor + 0.5) < armorPoints) {
			result += 0.5;
		}

		RETURN result;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Constants for accessing MEK files

const size_t MW2_MEK_SectionOffset[8] = {
   0x018, // Head, 6 criticals
   0x068, // Center Torso, 12 criticals
   0x090, // Left Torso, 12 criticals
   0x040, // Right Torso, 12 criticals
   0x0E0, // Left Arm, 12 criticals
   0x0B8, // Right Arm, 12 criticals
   0x108, // Left Leg, 6 criticals
   0x130}; // Right Leg, 6 criticals

const int criticalOffset = 12;

////////////////////////////////////////////////////////////////////////////////
// Help functions for loading MEK files

int Vehicle::findCriticalInRange(const MemoryBlock* mb, int start, int end)
{
	SET_SCOPE;
	int result = -1;

	for (int section = 0; section < getSectionCount(); section++) 
	{
		int base = MW2_MEK_SectionOffset[section];
		for (int critical = 0; 
			critical < getCriticalCount(section);
			++critical) 
		{
			DWORD offset = base+criticalOffset+2*critical;
			WORD val = mb->get<WORD>(offset);
			if (val >= start && val <= end) {
				result = val;
			}
		}
	}

	RETURN result;
}

// Check how many criticals in a MEK file are of type eqptID
size_t Vehicle::countCriticals (const MemoryBlock* mb, WORD eqptID)
{
	SET_SCOPE;
	size_t result = 0;

	for (int section = 0; section < getSectionCount(); section++) 
	{
		int base = MW2_MEK_SectionOffset[section];
		for (int critical = 0; critical < getCriticalCount(section); ++critical) {
			DWORD offset = base+criticalOffset+2*critical;
			WORD val = mb->get<WORD>(offset);
			if (val == eqptID) {
				result++;
			}
		}
	}

	RETURN result;
}

size_t Vehicle::countCriticals (const MemoryBlock* mb, WORD minID, WORD maxID)
{
	SET_SCOPE;
	size_t result = 0;

	for (int section = 0; section < 8; section++) 
		for (int critical = 0; critical < getCriticalCount(section); ++critical) {
			WORD val = mb->get<WORD>(MW2_MEK_SectionOffset[section]+criticalOffset+2*critical);
			if (val >= minID && val < maxID) {
				result++;
		}
	}

	RETURN result;
}

#define MechVMAddendumSig "MechVM MEK Addendum"

struct MechVMMEKAddendum {
    char sig[24];
    int heatSinkCount, heatSinkType, techBase, mekSubtype, engineRating, engineType;
};

void Vehicle::checkBasicMechProperties (const MemoryBlock* mb, bool& subType31stCC)
{
	SET_SCOPE;
	tonnage = mb->get<uint8_t>(0);
	int walkingHexes = mb->get<uint8_t>(4);
	engineRating = tonnage * walkingHexes;
	jumpJets = mb->get<uint8_t>(8);
	heatSinks = mb->get<uint8_t>(12); // unreliable, may be corrected later
	subType31stCC = false;
	techType = Tech_Type_IS_Level_1;
	int heatSinksInEngine = getInteger(Info_EngineRating) / 25;
	int firstHeadCritical = mb->get<WORD>(MW2_MEK_SectionOffset[0]+criticalOffset);

	if (firstHeadCritical == 5900)
		subType31stCC = true;

	size_t CritsPerHeatSink, totalHeatSinkCrits, endoCrits, ffCrits, engineCrits;

	// Read addendum, if present
	auto weaponsCount = mb->get<DWORD>(16);
	auto ammoCount = mb->get<DWORD>(20);
	size_t mekBaseLen = 0x158;
	size_t mekMinSize = mekBaseLen + 8 * weaponsCount + 8 * ammoCount + 1;

	bool addendumRead = false;
	size_t addendumOffset = mekMinSize;
	if (bgtextscan(mb->getPtr<char>(), MechVMAddendumSig, mekMinSize, mb->getSize(), addendumOffset)) 
	{
		MechVMMEKAddendum addendum;
		mb->read(addendumOffset, sizeof(MechVMMEKAddendum), &addendum);
		heatSinks = addendum.heatSinkCount;
		HeatSinkType = (WeaponType) addendum.heatSinkType;
		techType = addendum.techBase;
		engineRating = addendum.engineRating;
		EngineType = addendum.engineType;
		addendumRead = true; // not done yet: still need to check for internal structure & armor type
	}

	if (subType31stCC) {
		// MW2 supports standard and extra-large (XL) engines
		engineCrits = countCriticals (mb, 5850);
		ffCrits = countCriticals(mb, 9000, 9050);
		endoCrits = countCriticals (mb, 8000, 8050);
		techType = Tech_Type_Clan;
	} else {
		engineCrits = countCriticals (mb, 7850);
		endoCrits = countCriticals (mb, 9000, 9050);
		ffCrits = countCriticals(mb, 9500, 9550);
	}

	size_t ClanWeaponCrits = countCriticals (mb, 1, 3499);
	size_t ISWeaponCrits =  countCriticals (mb, 3500, 6999);
	if (ClanWeaponCrits > ISWeaponCrits) {
		techType = Tech_Type_Clan;
	}

	// Check for Endo Steel, Ferro Fibrous and XL engines
	// These are very reliable in determining whether we are
	// dealing with a IS/Clan Mech
	if (endoCrits >= 14) {
		InternalStructureType = WeaponType::IS_Endo_Steel;
		techType = Tech_Type_IS_Level_2;
	} else if (endoCrits >= 7) {
		InternalStructureType = WeaponType::Clan_Endo_Steel_Internals;
		techType = Tech_Type_Clan;
	} else 
		InternalStructureType = WeaponType::Unused_Critical_Slot;

	if (ffCrits == 7) {
		ArmorType = ARMOR_TYPE_FERRO_FIBROUS;
		techType = Tech_Type_Clan;
	} else if (ffCrits == 14) {
		ArmorType = ARMOR_TYPE_FERRO_FIBROUS;
		techType = Tech_Type_IS_Level_2;
	} else
		ArmorType = ARMOR_TYPE_STD;

	mMetersPerSecond = 4.5 * engineRating / tonnage;
	setMinMaxSpeed();

	if (addendumRead) {
		RETURN;
	}

	if (engineCrits == 10) {
		EngineType = ENGINE_TYPE_XL;
		techType = Tech_Type_Clan;
	} else if (engineCrits == 12) {
		EngineType = ENGINE_TYPE_XL; // Not in DOS versions of 31stcc, GBL?
		techType = Tech_Type_IS_Level_2;
	} else {
		EngineType = ENGINE_TYPE_STD;
	}

	if (subType31stCC) {
		// Determine number and type of heat sinks
		HeatSinkType = WeaponType::IS_Heat_Sink;
		CritsPerHeatSink = 0;
		totalHeatSinkCrits = 0;
		int heatSinkCrit = findCriticalInRange(mb, 6001, 6099);

		if (heatSinkCrit > 6000) {
			CritsPerHeatSink = countCriticals (mb, heatSinkCrit);
			totalHeatSinkCrits = countCriticals (mb, 6000, 6100);
		}

		if (CritsPerHeatSink == 0) {
			// Internal heat sinks only
			if (totalHeatSinkCrits == 0) {
				// No heat sink criticals: unclear whether there are heat sinks
				// that require tonnage (10 < #hs < engine / 25)
				HeatSinkType = WeaponType::Clan_Double_Heat_Sink;
				heatSinks /= 2; // convert double->single
				int minReasonableHS = std::min(10, heatSinksInEngine);
				if (heatSinks < minReasonableHS)
					heatSinks = minReasonableHS;
				if (heatSinks > heatSinksInEngine)
					heatSinks = heatSinksInEngine; 
			} else {
				// Error: first heat sink number skipped?
				heatSinks = heatSinksInEngine + totalHeatSinkCrits;
			}
		}
	} else {
		// Determine number and type of heat sinks
		CritsPerHeatSink = countCriticals (mb, 8001);
		totalHeatSinkCrits = countCriticals (mb, 8001, 8100);
		if (CritsPerHeatSink == 0) {
			// No heat sink criticals: unclear whether there are heat sinks
			// that require tonnage (10 < #hs < engine / 25)
			if (techType == Tech_Type_Clan) {
				HeatSinkType = WeaponType::Clan_Double_Heat_Sink;
				// Apparently, some MEK files store the number of effective heat sinks, 
				// while others store the actual number
				//heatSinks /= 2; // MEK file stores number of effective heat sinks, we need the actual number
			} else if (heatSinks > 2*heatSinksInEngine) {
				heatSinks = heatSinksInEngine; // no crits, no more heat sinks
				HeatSinkType = WeaponType::IS_Double_Heat_Sink;
			} else if (heatSinks > heatSinksInEngine && heatSinks % 2 == 0) {
				HeatSinkType = WeaponType::IS_Double_Heat_Sink;
				heatSinks /= 2; // MEK file stores number of effective heat sinks, we need the actual number
			} else {
				HeatSinkType = WeaponType::IS_Heat_Sink;
				int minReasonableHS = std::min(10, heatSinksInEngine);
				if (heatSinks < minReasonableHS)
					heatSinks = minReasonableHS;
			}
		}
	}

	// Determine number and type of heat sinks
	switch (CritsPerHeatSink) {
		//case 0: break; // treated above ...
	case 1:
		HeatSinkType = WeaponType::IS_Heat_Sink;
		heatSinks = heatSinksInEngine + totalHeatSinkCrits;
		break;
	case 2:
		HeatSinkType = WeaponType::Clan_Double_Heat_Sink;
		heatSinks = heatSinksInEngine + totalHeatSinkCrits / 2;
		techType = Tech_Type_Clan;
		break;
	case 3:
		HeatSinkType = WeaponType::IS_Double_Heat_Sink;
		heatSinks = heatSinksInEngine + totalHeatSinkCrits / 3;
		techType = Tech_Type_IS_Level_2;
		break;
	}

	RETURN;
}

WeaponType Vehicle::decodeMW2Critical (uint16_t ID, int HeatSinkType, bool subType31stCC)
{
	SET_SCOPE;
	WeaponType result = WeaponType::Unused_Critical_Slot;

	if (ID > 0) {
		int cmpID = (int) WeaponType::IS_Energy_Weapon_Base;
		while (result == WeaponType::Unused_Critical_Slot && cmpID < (int) WeaponsCount) {
			const MWEquipment* w = getWeaponStats((WeaponType)cmpID);
			if ((w->MW2_ID <= ID && ID < w->MW2_ID + w->MaxCount && !subType31stCC)
				||  (w->MW2_31st_ID <= ID && ID < w->MW2_31st_ID + w->MaxCount && subType31stCC))
			{
				result = (WeaponType)cmpID;
			} else {
				cmpID++;
			}
		}
	}

	RETURN result;
}

////////////////////////////////////////////////////////////////////////////////

void Vehicle::setGroup(int entry, int group)
{
	SET_SCOPE;
	if (entry < (int)weapons.size()) {
		if (group < MaxGroupCount)
			weapons[entry].group = group;
		else
			weapons[entry].group = 0;
	}
	RETURN;
}

int Vehicle::getGroup(int entry) const
{
	SET_SCOPE;
	if (entry < (int) weapons.size()) {
		RETURN weapons[entry].group;
	} else {
		RETURN 0;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Load config from MechWarrior 2 MEK file

bool Vehicle::loadFromMW2MEK (const MemoryBlock* mb) 
{
	SET_SCOPE;
	bool subType31stCC;
	checkBasicMechProperties (mb, subType31stCC);

	SET_OP;
	size_t offset = 0x158 + mb->get<DWORD>(0x10)*8 + mb->get<DWORD>(0x14)*8;
	if (offset > mb->getSize()) {
		COUT << "Not an MEK file or a corrupt one? " << std::endl;
		RETURN false;
	}

	// We may need to filter invalid chars ...
	//comment = BGCPString(mb->getPtr<char>(offset), mb->getSize()-offset, CP_ASCII);
	char c = 32;
	while (offset < mb->getSize() && c > 31) {
		c = mb->get<char>(offset);
		if (c > 31) {
			comment.add(&c);
			++offset;
		}
	}

	// Extract stats & criticals
	SET_OP;
	for (int i = 0; i < getSectionCount(); i++) {
		size_t base = MW2_MEK_SectionOffset[i];
		DWORD InternalStructure = mb->get<DWORD>(base+8);
		setInternalStructure (i, InternalStructure);
		DWORD FrontArmor = mb->get<DWORD>(base);
		DWORD RearArmor = mb->get<DWORD>(base+4);
		setArmor (i, FrontArmor, Vehicle::Armor_Front);
		setArmor (i, RearArmor, Vehicle::Armor_Rear);

		for (int j = 0; j < getCriticalCount(i); j++) {
			WORD ID = mb->get<WORD>(base+12+2*j);
			if (ID > 0) {
				WeaponType crit = decodeMW2Critical (ID, HeatSinkType, subType31stCC);
				setCriticals(i, j, crit);
			} else
				setCriticals(i, j, WeaponType::Unused_Critical_Slot);
		}
	}

	// Correct critical hits table
	if (techType == Tech_Type_Clan) {
		SET_OP;
		for (int i = 0; i < getSectionCount(); i++) {
			for (int j = 0; j < getCriticalCount(i); j++) {
				auto crit = getCriticals(i, j);
				if (crit == WeaponType::IS_Endo_Steel)
					setCriticals (i, j, WeaponType::Clan_Endo_Steel_Internals);
				else if (crit == WeaponType::IS_Ferro_Fibrous)
					setCriticals (i,j, WeaponType::Clan_Ferro_Fibrous);
				else if (crit == WeaponType::IS_Heat_Sink && HeatSinkType == WeaponType::Clan_Double_Heat_Sink)
					setCriticals (i,j, Clan_Double_Heat_Sink);
			}
		}
	} else if (HeatSinkType == WeaponType::IS_Double_Heat_Sink) {
		SET_OP;
		for (int i = 0; i < getSectionCount(); i++) {
			for (int j = 0; j < getCriticalCount(i); j++) {
				auto crit = getCriticals(i, j);
				if (crit == WeaponType::IS_Heat_Sink && HeatSinkType == WeaponType::IS_Double_Heat_Sink)
					setCriticals (i, j, WeaponType::IS_Double_Heat_Sink);
			}
		}
	}

	// Construct weapon table
	SET_OP;
	DWORD WeaponCount = mb->get<DWORD>(0x10);
	for (size_t i = 0; i < WeaponCount; ++i) {
		size_t base = 0x158 + i * 8;
		WORD wID = mb->get<WORD>(base);
		WeaponType weaponType = decodeMW2Critical (wID, HeatSinkType, subType31stCC);
		const MWEquipment* weapon = getWeaponStats(weaponType);
		if (weapon != nullptr) {
			MountedWeapon weaponSystem;
			weaponSystem.weaponType = weaponType;
			size_t weaponGroup = mb->get<WORD>(base+5) % MaxGroupCount;
			weaponSystem.criticals.clear();
			weaponSystem.destroyed = false;

			int j = 0;
			int k = 0;
			while (j < getSectionCount()) {
				size_t base = MW2_MEK_SectionOffset[j];
				while (k < getCriticalCount(j)) {
					WORD oID = mb->get<WORD>(base+12+2*k);
					if (wID == oID) { // Exact match needed
						weaponSystem.section = j;
						weaponSystem.lastFired = 0;
						weaponSystem.criticals.push_back(k);
					}
					++k;
				}
				++j;
				k = 0;
			}

			weaponSystem.group = weaponGroup;
			weapons.push_back(weaponSystem);
			weaponSystem.destroyed = false;
		}
	}

	SET_OP;
	fillAmmoTable();
	calcRealWeight();

	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////
// Save Mech config to MEK file
// Write Titanium MEK files only now

void Vehicle::saveToMW2MEK (const PathChar* mekFN)
{
   size_t ammoCount = 0;

   SET_SCOPE;
   int weaponCritCount [WeaponsCount];
   int weaponTableCount[WeaponsCount];
   for (size_t i = 0; i < WeaponsCount; ++i) {
      weaponCritCount[i] = 0;
      weaponTableCount[i] = 0;
   }

   // Count equipment criticals
   SET_OP;
   for (int i = 0; i < getSectionCount(); i++) {
      for (int j = 0; j < getCriticalCount(i); j++) {
         WORD ID = getCriticals(i, j);
         ++weaponCritCount[ID];
      }
   }

   SET_OP;
   comment.convertToCP(CP_ASCII);
   size_t mekBaseLen = 0x158;
   size_t mbSize = mekBaseLen + 8 * weapons.size() + 8 * ammoCount + sizeof(MechVMMEKAddendum) + comment.getByteCount();

   // Allocate MemoryBlock
   MemoryBlock* mb = new MemoryBlock(mbSize);
   mb->fill(0, mbSize, 0);

   // Set basic information
   mb->set<BYTE>(0, tonnage);
   int walkingHexes = engineRating / tonnage;
   mb->set<BYTE>(4, walkingHexes);
   //engineRating = tonnage * walkingHexes;
   mb->set<BYTE>(8, jumpJets);
   mb->set<BYTE>(12, (BYTE)heatSinks); // unreliable, may be corrected later
   mb->set<BYTE>(16, (BYTE)weapons.size());
   mb->set<BYTE>(20, (BYTE)ammoCount); // may be updated below

   if (HeatSinkType != WeaponType::IS_Heat_Sink)
      mb->set<BYTE>(12, 2*heatSinks);
   else
      mb->set<BYTE>(12, heatSinks);

   // Set stats & criticals
   SET_OP;
   for (int i = 0; i < getSectionCount(); i++) {
      size_t base = MW2_MEK_SectionOffset[i];
      mb->set<DWORD>(base, getArmor(i, Armor_Front));
      mb->set<DWORD>(base+4, getArmor(i, Armor_Rear));
      mb->set<DWORD>(base+8, getInternalStructure(i));
      mb->set<BYTE>(base+36, getCriticalCount(i));
      mb->set<BYTE>(base+38, 1);

      for (int j = 0; j < getCriticalCount(i); j++) {
          WeaponType ID = getCriticals(i, j);
		  const MWEquipment* w = getWeaponStats(ID);
		  WORD MW2ID = WeaponType::Unused_Critical_Slot;
		  if (w != NULL) {
			  MW2ID = w->MW2_ID;
			  if (w->Criticals > 0 && (w->flags & MWWeaponFlag_CountedInMEK) > 0) {
				  MW2ID += weaponTableCount[ID]/w->Criticals + 1;
				  ++weaponTableCount[ID];
			  }
		  }
		  mb->set<WORD>(base+12+2*j, MW2ID);
	  }
   }

   // Reset weaponTableCount
   SET_OP;
   for (size_t i = 0; i < WeaponsCount; ++i) {
	   const MWEquipment* w = getWeaponStats((WeaponType)i);
	   if (w != NULL)
		   if (w->Criticals != 0)
			   weaponTableCount[i] = weaponCritCount[i] / w->Criticals;
   }

   // Fill in weapons list
   SET_OP;
   size_t offset = mekBaseLen;
   for (size_t i = 0; i < weapons.size(); ++i) {
      int weaponID = 0;

      // Determine MW2 weapon/critical ID from critical hits table
      if (weapons[i].criticals.size() > 0) {
          int section = weapons[i].section;
          int crit = weapons[i].criticals[0];
          size_t base = MW2_MEK_SectionOffset[section];
          weaponID = mb->get<WORD>(base+12+2*crit);
      }

      mb->set<DWORD>(offset, weaponID);
      mb->set<DWORD>(offset+4, 0);
      mb->set<BYTE>(offset+5, getGroup(i));
	  offset += 8;
   }

   // Scan all sections for ammo and write the ammo table
   SET_OP;
   ammoCount = 0;
   //size_t offset = mekBaseLen + 8*weapons.size();
   for (int i = 0; i < getSectionCount(); ++i) {
      size_t base = MW2_MEK_SectionOffset[i];
      for (int j = 0; j < getCriticalCount(i); ++j) {
         int MW2ID = mb->get<WORD>(base+12+2*j);
         WeaponType ID = decodeMW2Critical(MW2ID, HeatSinkType, false);
         const MWEquipment* a = getWeaponStats(ID);

         if (a != NULL)
         if ((a->flags & MWWeaponFlag_Ammo) > 0)
         {
            // Find weapon type associated with this weapon
            // (there can be only one in MW2)
            int wCount = 0;
            size_t i = 0; 
            const MWEquipment * w = NULL;
            while (i < MaxAmmoTypes && w == NULL) {
                WeaponType wID = (WeaponType)(a->AmmoTypes[i]);
               w = getWeaponStats(wID);
               if (w != NULL) {
                  wCount = weaponCritCount[wID] / w->Criticals;
                  if (wCount == 0) {
                     w = NULL;
                  }
               }
               ++i;
            }

            int wFID = 0;
            if (wCount > 0) {
               wFID = w->MW2_ID;

               // Use MW2ID to map this ammo to a weapon
               int ammoNum = MW2ID - w->MW2_ID;
               wFID += ammoNum % wCount + 1;
            }

            // Write ammo/weapon pair entries
            mb->set<DWORD>(offset, MW2ID);
            mb->set<DWORD>(offset+4, wFID);
            offset += 8;
			++ammoCount;
         }
      }
   }

   // Write user comments
   SET_OP;
   if (comment.getByteCount() > 49)
      comment.setByteCount(49);
   if (comment.getByteCount() > 0) {
	   mb->write(offset, comment.getPtr<unsigned char>(), comment.getByteCount()+1);
	   offset += comment.getByteCount()+1;
   } else {
	   char zero = 0;
	   mb->write(offset, &zero, 1);
	   ++offset;
   }

   // Write addendum
   MechVMMEKAddendum addendum;
   bgstrcpy(addendum.sig, MechVMAddendumSig, 24);
   addendum.heatSinkCount = heatSinks;
   addendum.heatSinkType = HeatSinkType;
   addendum.techBase = techType;
   addendum.mekSubtype = 3;
   addendum.engineRating = engineRating;
   addendum.engineType = EngineType;
   mb->write(offset, &addendum, sizeof(addendum));

   // Update ammo count
   mb->set<BYTE>(20, (BYTE)ammoCount); // may be updated below

   mb->saveToFile(mekFN);
   RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Load entire mech

bool Vehicle::loadTexture(const PathChar* textureFN)
{
	SET_SCOPE;
	if (mVehicleTexture == nullptr) {
		mVehicleTexture = new Texture();
	}

	if (mVehicleTexture->loadFromFile(textureFN)) {
		mThingsToDeleteInDestructor.push_back(mVehicleTexture);
		applyTexture(mVehicleTexture);
		RETURN true;
	} else {
		safeDelete(mVehicleTexture);
		RETURN false;
	}
}

////////////////////////////////////////////////////////////////////////////////

void scaleVehicle(Vehicle* vehicle, double vehicleHeight)
{
	if (vehicle == nullptr || vehicleHeight < 0.01) {
		return;
	}

	SET_SCOPE;
	vehicle->cacheTransformedVertices(Matrix());
	Point3D min, max;
	vehicle->getMinCoords(min);
	vehicle->getMaxCoords(max);
	double oldH = max.y - min.y;

	if (oldH < 0.01) {
		COUT << "Vehicle scale error" << std::endl;
		RETURN;
	}

	double d = vehicleHeight / oldH;
	Matrix m = ScaleMatrix(d, d, d);
	m = m * TranslationMatrix(0, -min.y, 0);
	vehicle->applyMatrix(m);

	vehicle->getName().print();
	COUT << " height adjusted " << oldH << " -> " << vehicleHeight << std::endl;

	// After adjusting the scale, we may need to adjust the normals as well
	for (size_t i = 0; i < vehicle->getObjectCount(); ++i) {
		RenderableObject* ro = vehicle->getObject(i);
		Mesh* mesh = dynamic_cast<Mesh*>(ro);
		if (mesh != nullptr) {
			mesh->calculateNormals();
		}
	}
	RETURN;
}

// Count number of criticals stored in the current config
int Vehicle::countCriticals(WeaponType type)
{
	SET_SCOPE;
	int result = 0;
	for (int i = 0; i < getSectionCount(); ++i) {
		for (int j = 0; j < getCriticalCount(i); ++j) {
			if (getCriticals(i, j) == type) {
				++result;
			}
		}
	}
	RETURN result;
}

Vehicle* loadVehicle (const BGCPString& name, BGCPString& mvmkFN, BGCPString& partPath, BGCPString& textureFN,
	const BGCPString& faction)
{
	SET_SCOPE;
	XMLTree xml;
	if (!xml.loadFromFile(mvmkFN.getAsPath())) {
		RETURN NULL;
	}

	SET_OP;
	XMLTreeNode* root = xml.getRoot();
	if (root == nullptr) {
		BGCPString msg("XML apparently empty: ");
		msg += mvmkFN;
		msg.printLn();
		RETURN nullptr;
	}

	SET_OP;
	XMLTreeConstIterator iter(root);
	Vehicle * vehicle = NULL;
	if (iter.nodeNameEquals("mech")) {
		vehicle = new BipedMech();
	} else if (iter.nodeNameEquals("quad")) {
		vehicle = new QuadMech();
	} else if (iter.nodeNameEquals("wheeled")) {
		vehicle = new CombatVehicle(CombatVehicleTypes::wheeled);
	} else if (iter.nodeNameEquals("tracked")) {
		vehicle = new CombatVehicle(CombatVehicleTypes::tracked);
	} else if (iter.nodeNameEquals("hovercraft")) {
		vehicle = new CombatVehicle(CombatVehicleTypes::hover);
	} else if (iter.nodeNameEquals("submarine")) {
		vehicle = new VTOL(CombatVehicleTypes::submarine);
	} else if (iter.nodeNameEquals("vtol")) {
		vehicle = new VTOL(CombatVehicleTypes::vtol);
	} else if (iter.nodeNameEquals("turret")) {
		vehicle = new CombatVehicle(CombatVehicleTypes::turretVehicle);
	} else if (iter.nodeNameEquals("aerotech")) {
		vehicle = new AeroTech(AeroTech::Aerotech_Subtype::aerotech);
	} else if (iter.nodeNameEquals("aerodyne")) {
		vehicle = new AeroTech(AeroTech::Aerotech_Subtype::aerodyne);
	} else if (iter.nodeNameEquals("spherical")) {
		vehicle = new AeroTech(AeroTech::Aerotech_Subtype::spherical);
	} else {
		COUT << "Unknown vehicle type ";
		BGCPString nodeName;
		if (iter.getNodeName(nodeName)) {
			nodeName.printLn();
		}
		//vehicle = new Vehicle("Vehicle", VehicleID, nullptr, 2);
		RETURN nullptr; // Don't know what to instantiate ...
	}

	vehicle->setName(name);

	// Texture must be loaded first
	SET_OP;
	vehicle->loadTexture(textureFN.getAsPath());
	vehicle->setFaction(faction); // Needed early for choosing the faction logo

	SET_OP;
	if (!vehicle->loadFromXml(iter, partPath.getAsPath())) {
		safeDelete(vehicle);
	} else {
		vehicle->setMinMaxSpeed();
		double height = iter.getDoubleAttributeByName("mechHeight") + iter.getDoubleAttributeByName("height");
		if (height > 0.0) {
			scaleVehicle(vehicle, height);
		}
	}

	RETURN vehicle;
}

////////////////////////////////////////////////////////////////////////////////
// Adapted from MechShell::loadPlayerVehicle
// todo: better name, better summary

Vehicle* loadVehicle2(const BGCPString& chassisName, const BGCPString& configFN, 
	const BGCPString& textureFN, const BGCPString& faction)
{
	SET_SCOPE;
	BGCPString MVMKFNpart, BMPpart;
	BGCPString MVMKdir (getDataDir());
	MVMKdir.addPathSep(MECH_SUB_DIR);
	MVMKdir.addPathSep(chassisName);

	BGCPString MechName = chassisName;
	if (!configFN.empty()) {
		MechName += " - ";
		MechName += configFN;
	}

	SET_OP;
	BGCPString MVMKmask = MVMKdir;
	MVMKmask.addPathSep("*.mvmk");
	if (!FileList::findFirstMatch(MVMKmask.getAsPath(), MVMKFNpart)) {
		log("MVMK file not found: ", MVMKmask);
		RETURN nullptr;
	}

	SET_OP;
	BGCPString MVMKFN = MVMKdir;
	MVMKFN.addPathSep(MVMKFNpart);

	SET_OP;
	BGCPString BMPmask = MVMKdir;
	BMPmask.addPathSep("*.BMP");
	BGCPString BMPFN = MVMKdir;
	BGCPString fullTextureFN = MVMKdir;
	fullTextureFN.addPathSep(textureFN);
	if (fileExists(fullTextureFN.getAsPath())) {
		BMPFN = fullTextureFN;
	} else if (FileList::findFirstMatch(BMPmask.getAsPath(), BMPpart)) {
		BMPFN.addPathSep(BMPpart);
	} else {
		SET_OP;
		BMPmask = MVMKdir;
		BMPmask.addPathSep("*.png");
		if (FileList::findFirstMatch(BMPmask.getAsPath(), BMPpart)) {
			BMPFN.addPathSep(BMPpart);
		} else {
			log("No texture found in ", BMPmask);
		}
	}

	SET_OP;
	Vehicle* vehicle = ::loadVehicle(MechName, MVMKFN, MVMKdir, BMPFN, faction);
	if (vehicle != NULL) {
		vehicle->typeName = MechName;
		vehicle->MVMKFN = MVMKFN;
		vehicle->textureFN = BMPFN;

		if (!configFN.empty()) {
			BGCPString MEKFN = MVMKdir;
			MEKFN.addPathSep();
			MEKFN += configFN;
			vehicle->loadConfig(MEKFN.getAsPath());
		}
	}

	RETURN vehicle;
}

////////////////////////////////////////////////////////////////////////////////

DWORD Vehicle::getArmor(int section, int ArmorType) const
{
	return 0;
}

bool Vehicle::setArmor(int section, DWORD newVal, int ArmorType)
{
	return false;
}

void Vehicle::setInternalStructure(int section, DWORD newVal)
{
}

DWORD Vehicle::getInternalStructure(int section) const
{
	return 0;
}

double Vehicle::getWeight() const
{
	return 0;
}

// Check if a section may mount jump jets
bool Vehicle::mayMountJJs(int section)
{
	return false;
}

int Vehicle::countArmorPoints() const
{
	return 0;
}

// Remove weapon - returns number of criticals removed
int Vehicle::removeWeapon(int currSection, int critNo, WeaponType weaponNo, int CritCount)
{
	return 0;
}

// Add weapon
bool Vehicle::addWeapon(int currSection, WeaponType weaponNo)
{
	SET_SCOPE;
	if (currSection >= (int) mSections.size()) {
		return false;
	}

	auto* weapon = getWeaponStats(weaponNo);
	if (weapon == nullptr) {
		RETURN false;
	}

	auto weaponCrits = weapon->Criticals;
	MountedWeapon mountedWeapon;
	int firstCrit = mSections[currSection].criticals.size();

	for (int i = 0; i < weaponCrits; ++i) {
		mSections[currSection].criticals.push_back(weaponNo);
	}

	if ((weapon->flags & MWWeaponFlag_IncludeInWeaponList) > 0) {
		mountedWeapon.weaponType = weaponNo;
		for (int i = 0; i < weaponCrits; ++i) {
			mountedWeapon.criticals.push_back(i);
		}
		mountedWeapon.section = currSection;
		mountedWeapon.lastFired = 0;
		mountedWeapon.group = 0;
		mountedWeapon.shellsFiredInCurrentShot = 0;
		mountedWeapon.destroyed = false;
		weapons.push_back(mountedWeapon);
	} else if ((weapon->flags & MWWeaponFlag_Ammo) > 0) {
		MountedAmmo newAmmo;
		newAmmo.weaponType = weaponNo;
		newAmmo.section = currSection;
		newAmmo.remainingShots = weapon->ShotsPerTon;
		newAmmo.critical = firstCrit;
		ammo.push_back(newAmmo);
	}
	RETURN true;
}

void Vehicle::readSpecialAttributesFromRoot(XMLTreeConstIterator& iter)
{
	mMetersPerSecond = iter.getDoubleAttributeByName("metersPerSecond");
	mHealth = iter.getDoubleAttributeByName("health");
}

void Vehicle::readSpecialAttributes(XMLTreeConstIterator& iter, const std::vector<RenderableObject*>& geometries, const PathChar* mechDir)
{
}

// Add a logo to the main texture
void Vehicle::processLogoTag(XMLTreeConstIterator&iter)
{
	if (mVehicleTexture == nullptr) {
		return;
	}

	SET_SCOPE;
	double u1 = iter.getDoubleAttributeByName("u1");
	double v1 = iter.getDoubleAttributeByName("v1");
	double u2 = iter.getDoubleAttributeByName("u2");
	double v2 = iter.getDoubleAttributeByName("v2");

	// Load faction texture
	SET_OP;
	Texture logoTexture;
	BGCPString logoFN = getDataDir();
	logoFN.addPathSep("insignias");
	logoFN.addPathSep(mFaction + BGCPString(".png"));

	SET_OP;
	if (logoTexture.loadFromFile(logoFN.getAsPath())) {
		// Fill texture part u1..u2, v1..v2 with faction logo.
		// We need a stretch copy operation that can deal with transparency for this
		SET_OP;
		TextureDim tu1 = (int)(u1 * (mVehicleTexture->getWidth()-1));
		TextureDim tv1 = (int)(v1 * (mVehicleTexture->getHeight()-1));
		TextureDim tu2 = (int)(u2 * (mVehicleTexture->getWidth()-1));
		TextureDim tv2 = (int)(v2 * (mVehicleTexture->getHeight()-1));
		SET_OP;
		mVehicleTexture->stretchCopy(&logoTexture, 0, 0, logoTexture.getWidth()-1, logoTexture.getHeight()-1,
			tu1, tv1, tu2, tv2, true);
	}
	RETURN;
}

// Compute additional values after loading a config (like heat sinks)
void Vehicle::postLoadConfig()
{
}

// Read MVMK file contents
bool Vehicle::loadFromXml(XMLTreeConstIterator& iter, const PathChar* path)
{
	SET_SCOPE;
	BGCPString name;
	iter.getAttributeValueByName("name", name);
	COUT << "Loading ";
	name.print();
	COUT << " from " << path << std::endl;
	setName (name);

	// For parsing weapons using a config tag, we need to know the tech base
	BGCPString techBaseStr;
	techType = Tech_Type_IS_Level_1;
	if (iter.getAttributeValueByName("techBase", techBaseStr)) {
		BGCPString techBaseLC = techBaseStr.lowerStringASCII();
		if (techBaseLC == "is2") {
			techType = Tech_Type_IS_Level_2;
		} else if (techBaseLC == "clan") {
			techType = Tech_Type_Clan;
		}
	}

	SET_OP;
	tonnage = (int) iter.getIntAttributeValueByName("tonnage");
	mHealth = iter.getDoubleAttributeByName("health", mHealth);
	mLift = iter.getDoubleAttributeByName("lift", 0.0);
	readSpecialAttributesFromRoot(iter);

	// Load mech parts
	std::vector<RenderableObject*> geometries;
	for (size_t j = 0; j < iter.getChildCount(); j++) {
		SET_OP;
		iter.goToChild(j);

		if (iter.nodeNameEquals("geometry")) {
			SET_OP;
			BGCPString geometryPath(path, CP_SYSTEM);
			BGCPString geometryFN;
			iter.getAttributeValueByName("path", geometryFN);
			geometryPath.addPathSep(geometryFN);
			RenderableObject* geometry = new RenderableObject();
			if (geometry->loadFromObj(geometryPath.getAsPath())) {
				geometries.push_back(geometry);
			}
		} else if (iter.nodeNameEquals("object")) {
			SET_OP;
			placePart(iter, geometries, 0, path);
		} else if (iter.nodeNameEquals("cockpit-coords")) {
			SET_OP;
			cockpitCoords.x = iter.getDoubleAttributeByName("x", cockpitCoords.x);
			cockpitCoords.y = iter.getDoubleAttributeByName("y", cockpitCoords.y);
			cockpitCoords.z = iter.getDoubleAttributeByName("z", cockpitCoords.z);
		} else if (iter.nodeNameEquals("cockpit-layout") && GLEW_ARB_framebuffer_object) {
			SET_OP;
			mUseCockpitDisplays = true;
			for (size_t j = 0; j < iter.getChildCount(); j++) {
				iter.goToChild(j);
				if (iter.nodeNameEquals("heat")) {
					parseDisplayCoords(iter, heatDisplayCoords);
				} else if (iter.nodeNameEquals("jumpjets")) {
					parseDisplayCoords(iter, jumpJetDisplayCoords);
				} else if (iter.nodeNameEquals("speed")) {
					parseDisplayCoords(iter, speedDisplayCoords);
				} else if (iter.nodeNameEquals("weapons")) {
					parseDisplayCoords(iter, weaponDisplayCoords);
				} else if (iter.nodeNameEquals("armor")) {
					parseDisplayCoords(iter, armorDisplayCoords);
				} else if (iter.nodeNameEquals("map")) {
					parseDisplayCoords(iter, mapDisplayCoords);
				} else if (iter.nodeNameEquals("altimeter")) {
					parseDisplayCoords(iter, altimeterDisplayCoords);
				} else if (iter.nodeNameEquals("artificial_horizon")) {
					parseDisplayCoords(iter, artificialHorizonDisplayCoords);
				} else if (iter.nodeNameEquals("viewpanel")) {
					DisplayComponentCoords viewpanelCoords;
					parseDisplayCoords(iter, viewpanelCoords);
					mViewpanels.push_back(viewpanelCoords.u1);
					mViewpanels.push_back(viewpanelCoords.v1);
					mViewpanels.push_back(viewpanelCoords.u2);
					mViewpanels.push_back(viewpanelCoords.v2);
				}
				iter.goToParent();
			}
		} else if (iter.nodeNameEquals("logo")) {
			processLogoTag(iter);
		} else if (iter.nodeNameEquals("config")) {
			// todo: if this tag is encountered, do not look for an additional MEK file ...
			SET_OP;
			for (size_t k = 0; k < iter.getChildCount(); ++k) {
				iter.goToChild(k);
				BGCPString sectionName;
				iter.getNodeName(sectionName);
				int section = getSectionByName(sectionName);
				if (section >= 0) {
					auto armor = iter.getIntAttributeValueByName("armor");
					setArmor(section, (DWORD) armor, Armor_Front);

					if (iter.getChildCount() > 0) {
						iter.goToChild(0);
						BGCPString weapons = iter.getText();
						char eolChars[3] = {10, 13, 0};
						auto weaponList = weapons.split(eolChars, false);
						for (size_t l = 0; l < weaponList.size(); ++l) {
							BGCPString weaponName = weaponList[l].removeWhitespace();
							if (!weaponName.empty()) {
								if (!addWeaponByName(section, weaponName)) {
									name.print();
									COUT << " uses unknwon weapon ";
									weaponName.printLn();
								}
							}
						}
						iter.goToParent();
					}
				}

				iter.goToParent();
			}
		} else {
			readSpecialAttributes(iter, geometries, path);
		}
		iter.goToParent();
	}

	postLoadConfig();

	RETURN true;
}

// Check - and try to correct - config problems 
// Returns true on success, and an error string otherwise
bool Vehicle::check(BGCPString& errors)
{
	errors.clear();
	return true;
}

double Vehicle::calcRealWeight()
{
	return tonnage;
}

////////////////////////////////////////////////////////////////////////////////

void Vehicle::setActiveWeaponGroup(int groupNo)
{
	if (mActiveWeapon < (int)weapons.size() && groupNo < MaxGroupCount) {
		MountedWeapon& mountedWeapon = weapons[mActiveWeapon];
		mountedWeapon.group = groupNo;
	}
}

void Vehicle::createShot(MountedWeapon& mountedWeapon, DWORD now)
{
	SET_SCOPE;
	WeaponType weaponType = mountedWeapon.weaponType;
	const MWEquipment* weaponStats = getWeaponStats(weaponType);
	MechSim* mechSim = getMechSim();
	Mesh * projectilePrototype = mechSim->getShotPrototype(weaponStats->category);

	// We need to create a deep copy because instantiated geometry cannot store transformed geometry of all instances ...
	Mesh* projectile = new Mesh();
	projectile->copyMesh(projectilePrototype);

	if (projectile == nullptr) {
		RETURN;
	}

	int vehiclePart = section2part(mountedWeapon.section, preferLowerArms);
	Matrix m;
	getPartMatrix(vehiclePart, m);
	Point3D rayStart = m.Transform(mountedWeapon.position + Point3D(0,0,1));
	Point3D rayEnd   = m.Transform(mountedWeapon.position + Point3D(0,0,2));

	Shot* newShot = new Shot();
	newShot->shooter = this;
	newShot->triggeredWhen = now;
	newShot->weapon = &mountedWeapon;
	newShot->flightDir = rayEnd - rayStart;
	newShot->flightDir.unitLength();
	newShot->velocity = weaponStats->metersPerSecond + getSpeed();
	newShot->position = rayStart + 5.0 * newShot->flightDir; // factor 5 prevents gauss projectiles from hitting own vehicle ...
	newShot->flightDir = newShot->velocity * newShot->flightDir;
	newShot->damageCarried = ((double)weaponStats->MaxDamage) / weaponStats->ammoUsedPerShot;
	projectile->color = weaponStats->shotColor;
	newShot->addChild(projectile);
	mechSim->addShot(newShot);
	++mountedWeapon.shellsFiredInCurrentShot;
	RETURN;
}

bool Vehicle::canFireWeapon(int weapon, DWORD now)
{
	SET_SCOPE;
	bool result = false;

	if (weapon < (int) weapons.size()) {
		MountedWeapon& mountedWeapon = weapons[weapon];
		if (!mountedWeapon.destroyed) {
			const MWEquipment* weapon = getWeaponStats(mountedWeapon.weaponType);
			if (mountedWeapon.lastFired == 0 || mountedWeapon.lastFired + 1000.0 * (weapon->fireDuration + weapon->coolDown) < now) {
				MechSim* mechSim = getMechSim();
				auto env = mechSim->getEnvironment();
				if (((env == normal)      && ((weapon->flags & MWweaponFlag_WorksOverWater) > 0))
				||  ((env == space)       && ((weapon->flags & MWweaponFlag_WorksOverWater) > 0))
				||  ((env == underwater ) && ((weapon->flags & MWweaponFlag_WorksUnderwater) > 0)))
				{
					result = true;
				}
			}
		}
	}

	RETURN result;
}

void Vehicle::fire(MountedWeapon& mountedWeapon, DWORD now)
{
	// Check if weapon has ammo left
	SET_SCOPE;
	MechSim* mechSim = getMechSim();
	const MWEquipment* weapon = getWeaponStats(mountedWeapon.weaponType);
	auto ammo = ammoAvailable(mountedWeapon.weaponType);
	if (weapon->ammoUsedPerShot > ammo) {
		RETURN;
	}

	if (mountedWeapon.lastFired > 0 && mountedWeapon.lastFired + 1000.0 * (weapon->fireDuration + weapon->coolDown) >= now) {
		// Play a sound to indicate that the weapon is still cooling down
		mechSim->playSound("MECFSTX2.SFL");
		RETURN;
	}

	if (mountedWeapon.weaponType == CE_BOMB_100) {
		auto bombs = countWeaponInAllSections(CE_BOMB_100);
		mechSim->applyAreaDamage(getPosition(), bombs * weapon->MaxDamage, weapon->MaxRangeInMeters, this);
		setStatus(VehicleStatus::destroyed);
		mechSim->playSound(weapon->soundWhenFired);
		RETURN;
	}

	mountedWeapon.lastFired = now;
	const MWEquipment* baseWeapon = getWeaponStats(mountedWeapon.weaponType);
	if (weapon->ammoUsedPerShot > 0) {
		expendAmmo(mountedWeapon.weaponType, weapon->ammoUsedPerShot);
		mountedWeapon.shellsFiredInCurrentShot = 0;

		if (baseWeapon->ammoUsedPerShot > 0) {
			createShot(mountedWeapon, now);
		}
	}

	if (bgstrlen(baseWeapon->soundWhenFired) > 0) {
		mechSim->playSound(baseWeapon->soundWhenFired);
	}
	RETURN;
}

void Vehicle::fireWeapons(bool groupFire, DWORD now)
{
	if (groupFire){
		for (size_t i = 0; i < weapons.size(); ++i) {
			MountedWeapon& mountedWeapon = weapons[i];
			if (mountedWeapon.group == mActiveWeapon && canFireWeapon(i, now)) {
				fire(mountedWeapon, now);
			}
		}
	} else {
		if (mActiveWeapon < (int) weapons.size()) {
			MountedWeapon& weapon = weapons[mActiveWeapon];
			fire(weapon, now);
		}
	}

	cycleWeapon(groupFire);
}

void Vehicle::cycleWeapon(bool groupFire)
{
	if (groupFire) {
		mActiveWeapon = (mActiveWeapon + 1) % MaxGroupCount;
	} else {
		int wc = getWeaponListEntryCount();
		if (wc > 0) {
			mActiveWeapon = (mActiveWeapon + 1) % getWeaponListEntryCount();
		}
	}
}

const MWEquipment* Vehicle::getBaseWeapon(size_t i) const
{
	if (i < (int)weapons.size()) {
		WeaponType weaponType = weapons[i].weaponType;
		return getWeaponStats(weaponType);
	} else {
		return nullptr;
	}
}

WeaponType Vehicle::getBaseWeaponType(size_t i) const
{
	if (i < (int)weapons.size()) {
		return weapons[i].weaponType;
	} else {
		return Unused_Critical_Slot;
	}
}

double Vehicle::getLastFireTime(size_t i) const
{
	if (i < (int)weapons.size()) {
		return weapons[i].lastFired;
	} else {
		return 0.0;
	}
}

// Fill ammo table (call once)
void Vehicle::fillAmmoTable()
{
	ammo.clear();

	for (int i = 0; i < getSectionCount(); ++i) {
		for (int j = 0; j < getCriticalCount(i); ++j) {
			WeaponType ID = getCriticals(i, j);
			const MWEquipment* a = getWeaponStats(ID);

			if (a != NULL) {
				if ((a->flags & MWWeaponFlag_Ammo) > 0) {
					MountedAmmo mountedAmmo;
					mountedAmmo.section = i;
					mountedAmmo.critical = j;
					mountedAmmo.weaponType = ID;
					mountedAmmo.remainingShots = a->ShotsPerTon;
					ammo.push_back(mountedAmmo);
				}
			}
		}
	}
}

// Count how much ammo is left for a shot of weaponType
unsigned int Vehicle::ammoAvailable(WeaponType weaponType) const
{
	SET_SCOPE;
	unsigned int result = 0;

	for (size_t i = 0; i < ammo.size(); ++i) {
		const auto& ammoBin = ammo[i];
		if (ammoCompatible(weaponType, ammoBin.weaponType)) {
			result += ammoBin.remainingShots;
		}
	}

	RETURN result;
}

// Call after a shot to mark ammo as used
void Vehicle::expendAmmo(WeaponType weaponType, unsigned int ammoNeeded)
{
	for (size_t i = 0; i < ammo.size(); ++i) {
		auto& ammoBin = ammo[i];
		if (ammoCompatible(weaponType, ammoBin.weaponType)) {
			if (ammoBin.remainingShots >= ammoNeeded) {
				ammoBin.remainingShots -= ammoNeeded;
				return;
			} else if (ammoBin.remainingShots > 0) {
				ammoNeeded -= ammoBin.remainingShots;
				ammoBin.remainingShots = 0;
			}
		}
	}
}

int Vehicle::getEffectiveHeatSinks() const
{
	int heatSinks = getInteger(Vehicle::Info_Heat_Sinks);
	int heatSinkType = getInteger(Vehicle::Info_Heat_Sink_Type);

	if (heatSinkType != IS_Heat_Sink) {
		heatSinks *= 2;
	}

	return heatSinks;
}

// Add geometry for the loadout of a specific mech configuration
void Vehicle::updateLoadoutGeometry()
{
	SET_SCOPE;
	if (criticalLayouts.size() == 0) {
		// Loadout layout information required ...
		RETURN;
	}

	int sections = getSectionCount();
	int weapons = getMountedWeaponCount();

	// Iterate over the sections to place the weapons along the slots in a section 
	for (int i = 0; i < sections; ++i) {
		double criticalInSection = 0;
		for (int j = 0; j < weapons; ++j) {
			MountedWeapon* weapon = getMountedWeapon(j);
			int partNum = section2part(weapon->section, preferLowerArms);
			Mesh* sectionNode = dynamic_cast<Mesh*>(getObject(partNum));
			if (weapon->section == i) {
				auto weaponType = weapon->weaponType;
				const MWEquipment* weaponStats = getWeaponStats(weaponType);
				if (weaponStats->geometry != nullptr
				&&  weaponStats->xreps > 0
				&&  weaponStats->yreps > 0
				&&  sectionNode != nullptr
				&&  weapon->section < (int)criticalLayouts.size())
				{
					for (int x = 0; x < weaponStats->xreps; ++x) {
						for (int y = 0; y < weaponStats->yreps; ++y) {
							Mesh* weaponGeometryCopy = new Mesh("Mesh", MeshID, sectionNode);
							weaponGeometryCopy->copyMesh(weaponStats->geometry);
							Point3D pos = criticalLayouts[weapon->section].start + criticalLayouts[weapon->section].direction*(y+criticalInSection); 
							if (weaponStats->xreps > 1) {
								pos += (x-0.5*(weaponStats->xreps-1))*Point3D(0.25, 0, 0);
							} else {
								pos += 0.25 * weaponStats->Criticals*Point3D(0, 0.25, 0);
							}
							double weaponScale = weaponStats->weaponScale;
							Matrix m = TranslationMatrix(pos) * ScaleMatrix(weaponScale, weaponScale, weaponScale);
							weaponGeometryCopy->setMatrix(m);
							weapon->position = pos;
						}
					}
					criticalInSection += std::max(weaponStats->yreps, weaponStats->Criticals) + 0.25;
				}
			}
		}
	}
	RETURN;
}

// Query mounted weapons
size_t Vehicle::getMountedWeaponCount() const
{
	return weapons.size();
}

MountedWeapon* Vehicle::getMountedWeapon(size_t num)
{
	SET_SCOPE;
	if (num < weapons.size()) {
		RETURN &weapons[num];
	} else {
		RETURN nullptr;
	}
}

const MountedWeapon* Vehicle::getMountedWeapon(size_t num) const
{
	SET_SCOPE;
	if (num < weapons.size()) {
		RETURN &weapons[num];
	} else {
		RETURN nullptr;
	}
}

MountedWeapon* Vehicle::getMountedWeapon(int section, int critical)
{
	SET_SCOPE;
	for (size_t i = 0; i < weapons.size(); ++i) {
		MountedWeapon& weapon = weapons[i];
		if (weapon.section == section) {
			if (std::find(weapon.criticals.begin(), weapon.criticals.end(), critical) != weapon.criticals.end()) {
				RETURN &weapon;
			}
		}
	}

	RETURN nullptr;
}

const MountedWeapon* Vehicle::getMountedWeapon(int section, int critical) const
{
	SET_SCOPE;
	for (size_t i = 0; i < weapons.size(); ++i) {
		const MountedWeapon& weapon = weapons[i];
		if (weapon.section == section) {
			if (std::find(weapon.criticals.begin(), weapon.criticals.end(), critical) != weapon.criticals.end()) {
				RETURN &weapon;
			}
		}
	}

	RETURN nullptr;
}

// Parse display component coords from XML
void Vehicle::parseDisplayCoords(XMLTreeConstIterator& iter, DisplayComponentCoords& coords)
{
	if (iter.hasAttribute("u1"))
		coords.u1 = iter.getDoubleAttributeByName("u1");
	if (iter.hasAttribute("v1"))
		coords.v1 = iter.getDoubleAttributeByName("v1");
	if (iter.hasAttribute("u2"))
		coords.u2 = iter.getDoubleAttributeByName("u2");
	if (iter.hasAttribute("v2"))
		coords.v2 = iter.getDoubleAttributeByName("v2");
}

// Get fire arc for section
Point3D Vehicle::getFireArc(int section) const
{
	return Point3D();
}

// Return the number of the part corresponding to a section
// used by updateLoadoutGeometry() to find the mesh corresponding to the mech section
int Vehicle::section2part(int section, bool allowLowerArms)
{
	return section; 
}

bool Vehicle::useCockpitDisplays()
{
	return mUseCockpitDisplays; 
}

Texture* Vehicle::getTexture()
{
	return mVehicleTexture;
}

Vehicle* findVehicleParent(RenderableObject* hitObject)
{
	SET_SCOPE;
	while (hitObject != nullptr) {
		Vehicle* hitVehicle = dynamic_cast <Vehicle*> (hitObject->getParent());
		if (hitVehicle != nullptr) {
			RETURN hitVehicle;
		} else {
			hitObject = hitObject->getParent();
		}
	}

	RETURN nullptr;
}

// Process weapon fire: render lasers, process laser damage, emit bullets and missiles
void Vehicle::processWeaponFire(DWORD now, RenderableObject* scene, double delta)
{
	SET_SCOPE;
	for (size_t i = 0; i < weapons.size(); ++i) {
		MountedWeapon& weapon = weapons[i];
		const MWEquipment* baseWeapon = getWeaponStats(weapons[i].weaponType);
		if (weapon.lastFired > 0 
		&&  weapon.lastFired + 1000 * baseWeapon->fireDuration > now) {
			if (baseWeapon->ammoUsedPerShot == 0) {
				// Energy weapon is fired
				int vehiclePart = section2part(weapon.section, preferLowerArms);
				Matrix m;
				getPartMatrix(vehiclePart, m);
				Point3D rayStart = m.Transform(weapon.position);
				Point3D rayEnd   = m.Transform(weapon.position + 
					baseWeapon->MaxRangeInMeters * Point3D(0,0,1));

				// See where lightning strikes
				Point3D rayStart2 = m.Transform(weapon.position + 10.0 * Point3D(0,0,1)); // 10.0: prevent the vehicle from hitting itself ...
				double dist = 5000;
				RenderableObject* hitObject = traverseScene(rayStart2, rayEnd, scene, dist, rayEnd);
				if (hitObject != nullptr && dist < baseWeapon->MaxRangeInMeters) {
					// Transfer damage to the target!
					double damage = baseWeapon->MaxDamage * delta / baseWeapon->fireDuration;
					Vehicle* hitVehicle = findVehicleParent(hitObject);
					if (hitVehicle != nullptr) {
						hitVehicle->receiveDamage(hitObject, damage, this, rayEnd);
					} else {
						GameObject* gameObject = dynamic_cast<GameObject*>(hitObject);
						if (gameObject != nullptr) {
							gameObject->receiveDamage(hitObject, damage, this, rayEnd);
						}
					}
				} else {
					rayEnd = m.Transform(weapon.position + baseWeapon->MaxRangeInMeters * Point3D(0,0,1));
				}

				if (rayEnd.y < 0.0) {
					Ray ray(rayStart, rayEnd);
					Plane ground(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0, 0, 1));
					ray.intersects(ground, rayEnd);
				}

				glDisable(GL_TEXTURE_2D);
				glDisable(GL_LIGHTING);
				glBegin(GL_LINES);
				baseWeapon->shotColor.glColor();
				rayStart.glVertex();
				rayEnd.glVertex();
				glEnd();
			} else {
				double shellsThatShouldHaveBeenFiredByNow = baseWeapon->ammoUsedPerShot * (now - weapon.lastFired) / (1000.0 * baseWeapon->fireDuration);
				if (weapon.shellsFiredInCurrentShot < (int) baseWeapon->ammoUsedPerShot
				&&  weapon.shellsFiredInCurrentShot < shellsThatShouldHaveBeenFiredByNow)
				{
					createShot(weapon, now);
				}
			}
		}
	}
	RETURN;
}

// Get matrix used for stuff mounted on a vehicle part (or weapons fire)
bool Vehicle::getPartMatrix(int partNo, Matrix& m)
{
	if (partNo < (int) getObjectCount()) {
		m = getObject(partNo)->getMatrix();
		return true;
	} else {
		m = TranslationMatrix(getPosition()); 
		AbstractVehicleAI* ai = (AbstractVehicleAI*)getAI();
		if (ai != nullptr) {
			double angle = mHeading;
			m = m * RotationMatrix(M_PI* angle / 180.0, 0, 1.0, 0);
		}
		return false;
	}
}

// Return the section corresponding to a limb
int Vehicle::limb2section(int limb)
{
	return limb;
}

////////////////////////////////////////////////////////////////////////////////
// Copy mech part from previously loaded scene graph

bool Vehicle::copyGeometry(const std::vector<RenderableObject*>& geometries, XMLTreeConstIterator& iter, int partNumber, Texture* texture)
{
	SET_SCOPE;
	if (partNumber >= (int) getObjectCount()) {
		RETURN false;
	}

	BGCPString geometryName;
	iter.getAttributeValueByName("geometry", geometryName);

	SET_OP;
	RenderableObject* childToCopy = nullptr;
	for (size_t i = 0; i < geometries.size(); ++i) {
		RenderableObject* ro = geometries[i]->findChildByName(geometryName);
		if (ro != nullptr) {
			childToCopy = ro;
		}
	}
	if (childToCopy == nullptr) {
		RETURN false;
	}

	Mesh* meshToCopy = dynamic_cast<Mesh*>(childToCopy);
	if (meshToCopy == nullptr) {
		RETURN false;
	}

	if (partNumber >= 0) {
		SET_OP;
		double jx = iter.getDoubleAttributeByName("jx");
		double jy = iter.getDoubleAttributeByName("jy");
		double jz = iter.getDoubleAttributeByName("jz");
		m_joint[partNumber] = Point3D(jx, jy, jz);
	}

	SET_OP;
	double tx = iter.getDoubleAttributeByName("tx");
	double ty = iter.getDoubleAttributeByName("ty");
	double tz = iter.getDoubleAttributeByName("tz");
	Matrix m = TranslationMatrix(tx, ty, tz);

	double sx = iter.getDoubleAttributeByName("sx", 1.0);
	double sy = iter.getDoubleAttributeByName("sy", 1.0);
	double sz = iter.getDoubleAttributeByName("sz", 1.0);

	if (sx != 1.0 || sy != 1.0 || sz != 1.0) {
		m = m * ScaleMatrix(sx, sy, sz);
	}

	double rx = iter.getDoubleAttributeByName("rx");
	if (rx != 0.0) {
		m = m * RotationMatrix(M_PI * rx / 180.0, 1.0, 0.0, 0.0);
	}

	// Set faction logo?
	if (iter.hasAttribute("logo") && childToCopy != nullptr && texture != nullptr) {
		// Load faction texture
		Texture logoTexture;
		BGCPString logoFN = getDataDir();
		logoFN.addPathSep("insignias");
		logoFN.addPathSep(mFaction + BGCPString(".png"));
		if (logoTexture.loadFromFile(logoFN.getAsPath())) {
			for (size_t j = 0; j < meshToCopy->getPolygonCount(); ++j) {
				MeshPolygon* poly = meshToCopy->getPolygon(j);
				if (poly != nullptr && poly->getVertexCount() > 2) {
					// Initialize u1, u2, v1, v2 from the first polygon vertex
					unsigned int texI = poly->getTexCoordIndex(0);
					double u1, v1;
					meshToCopy->getTexCoords(0, u1, v1);
					double u2 = u1;
					double v2 = v1;

					// Update u1, v1, u2, v2, for the remaining vertices
					for (unsigned int k = 1; k < poly->getVertexCount(); ++k) {
						unsigned int texI = poly->getTexCoordIndex(k);
						double u, v;
						meshToCopy->getTexCoords(k, u, v);
						min_max_update(u1, u2, u);
						min_max_update(v1, v2, v);
					}

					// Fill texture part u1..u2, v1..v2 with faction logo.
					// We need a stretch copy operation that can deal with transparency for this
					TextureDim tu1 = (int)(u1 * (texture->getWidth()-1));
					TextureDim tv1 = (int)(v1 * (texture->getHeight()-1));
					TextureDim tu2 = (int)(u2 * (texture->getWidth()-1));
					TextureDim tv2 = (int)(v2 * (texture->getHeight()-1));
					texture->stretchCopy(&logoTexture, 0, logoTexture.getHeight()-1, logoTexture.getWidth()-1, 0,
						tu1, tv1, tu2, tv2, false);
				}
			}
		}
	}

	if (childToCopy != nullptr) {
		SET_OP;
		double scaleNormals = iter.getDoubleAttributeByName("scaleNormals", 1.0);
		addGeometry(partNumber, meshToCopy, m, texture);
		RETURN true;
	} else {
		SET_OP;
		COUT << "Limb not found: ";
		geometryName.printLn();
		RETURN false;
	}
}

void Vehicle::placePart(XMLTreeConstIterator& iter, const std::vector<RenderableObject*>& geometries, int partNumber, const PathChar* mechDir)
{
	SET_SCOPE;
	Texture* partTexture = mVehicleTexture;
	BGCPString textureFN;
	if (iter.getAttributeValueByName("texture", textureFN)) {
		partTexture = new Texture;
		BGCPString fullFN(mechDir, CP_SYSTEM);
		fullFN.addPathSep(textureFN);
		if (partTexture->loadFromFile(fullFN.getAsPath())) {
			mThingsToDeleteInDestructor.push_back(partTexture);
		} else {
			safeDelete(partTexture);
			partTexture = mVehicleTexture;
		}
	}

	SET_OP;
	BGCPString shadowProxyPart;
	if (iter.getAttributeValueByName("shadowProxy", shadowProxyPart)) {
		Mesh* oldGeo = nullptr;
		if (partNumber >= 0) {
			oldGeo = dynamic_cast <Mesh*> (getObject(partNumber));
		} else {
			oldGeo = this;
		}
		RenderableObject* ro = nullptr;
		for (size_t i = 0; i < geometries.size(); ++i) {
			RenderableObject* ro2 = geometries[i]->findChildByName(shadowProxyPart);
			if (ro2 != nullptr) {
				ro = ro2;
			}
		}

		bool found = false;
		if (ro != nullptr) {
			Mesh* proxyToCopy = dynamic_cast<Mesh*>(ro);
			if (proxyToCopy != nullptr) {
				oldGeo->copyShadowProxy(proxyToCopy);
				found = true;
			}
		}

		if (!found) {
			COUT << "Shadow proxy not found: ";
			shadowProxyPart.printLn();
		}
	}

	copyGeometry(geometries, iter, partNumber, partTexture);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Add geometry for a vehicle part

void Vehicle::addGeometry (int geometryType, Mesh* geometry, const Matrix& m, Texture* texture)
{
	SET_SCOPE;
	Mesh* oldGeo = nullptr;
	if (geometryType >= 0) {
		oldGeo = dynamic_cast <Mesh*> (getObject(geometryType));
	} else {
		oldGeo = this;
	}

	if (oldGeo == nullptr) {
		RETURN;
	}

	oldGeo->setTexture(texture);
	size_t basePoints = oldGeo->getVertexCount();

	for (size_t i = 0; i < geometry->getVertexCount(); i++) {
		Point3D p;
		geometry->getVertex(i, p);
		p = m.Transform(p);
		oldGeo->addPoint(p);
	}

	size_t texCoordBase = oldGeo->getTexCoordCount();
	for (size_t i = 0; i < geometry->getTexCoordCount(); i++) {
		double u, v;
		geometry->getTexCoords(i, u, v);
		oldGeo->addTexCoords(u, v);
	}

	for (size_t i = 0; i < geometry->getPolygonCount(); i++) {
		MeshPolygon* poly = geometry->getPolygon(i);
		size_t polySize = poly->getVertexCount();
		MeshPolygon* newPoly = oldGeo->addPolygon(polySize);
		newPoly->transformedNormal = poly->transformedNormal;

		for (size_t j = 0; j < polySize; j++) {
			unsigned int k = poly->getVertexIndex(j);
			newPoly->setVertexIndex(j, k + basePoints);
			auto ti = poly->getTexCoordIndex(j);
			newPoly->setTexCoordIndex(j, ti+texCoordBase);
		}
	}
	RETURN;
}

int Vehicle::getSectionCount() const
{
	return mSections.size();
}

BGCPString Vehicle::getSectionName(int i) const
{
	if (i >= 0 && (size_t) i < mSections.size()) {
		return mSections[i].sectionName;
	} else {
		return BGCPString();
	}
}

int Vehicle::getCriticalCount(int section) const
{
	if (section >= 0 && (size_t) section < mSections.size()) {
		return mSections[section].criticals.size();
	} else {
		return 0;
	}
}

WeaponType Vehicle::getCriticals(int section, int criticalSlot) const
{
	if (section < 0 || section >= getSectionCount())
		return WeaponType::Unused_Critical_Slot;
	if (criticalSlot < 0 || criticalSlot >= getCriticalCount(section))
		return WeaponType::Unused_Critical_Slot;

	return mSections[section].criticals[criticalSlot];
}

void Vehicle::setCriticals (int section, int slot, WeaponType weaponType)
{
	SET_SCOPE;
	if (section < 0 || section >= getSectionCount()) {
		RETURN;
	}
	if (slot < 0 || slot >= getCriticalCount(section)) {
		RETURN;
	}

	mSections[section].criticals[slot] = weaponType;
	RETURN;
}

// Set current animation matrices
void Vehicle::setMatrices(double speed, DWORD missionTime)
{
	SET_SCOPE;
	Point3D pos = getPosition();
	TranslationMatrix t1 (pos.x, pos.y, pos.z);
	Matrix M = t1 * Matrix(p_left, p_up, p_front);

	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		ro->setMatrix(M);
	}
	RETURN;
}

// Get number of engine hits
int Vehicle::getEngineHits() const
{
	return mEngineHits;
}

// Mobility may be reduced by damage, it starts at 1.0
double Vehicle::getMobility() const
{
	return mMobility;
}

// Get maximum speed in meters per second currently supported by the vehicle
double Vehicle::getMaxSpeed() const
{
	return mMetersPerSecond * mMobility;
}

// Function to notify the Vehicle that it is taking damage
void Vehicle::receiveDamage(RenderableObject* limb, double damage, GameObject* sender, const Point3D& pointOfImpact)
{
	if (isDestroyed()) {
		return;
	}

	SET_SCOPE;
	mDamageReceived += damage;
	if (mDamageReceived >= mHealth) {
		color = Color(0.0, 0.0, 0.0);
		MechSim* mechSim = getMechSim();
		mechSim->updateObjectStatus(mID, SubObjectiveType::attack, false);
		//mMobility = 0.0;
		mLift = 0.0;
		setStatus(destroyed);
	}
	RETURN;
}

// Help function for parsing that translates a section name to a number
// We cannot simply compare with the section names, because these may contain spaces
// and may be translated
int Vehicle::getSectionByName(const BGCPString& str)
{
	return -1;
}

// Parse weapon and add it to section
bool Vehicle::addWeaponByName(int section, const BGCPString& weaponName)
{
	if (weaponName.empty()) {
		return false;
	}

	// Let's first see if we can find a weapon with that name
	SET_SCOPE;
	WeaponType scanStartVal = IS1_Flamer;
	WeaponType scanEndVal = IS_Sword;

	if (techType == Tech_Type_Clan) {
		scanStartVal = Clan_Energy_Weapon_Base;
		scanEndVal = Clan_MASC;
	}

	// Let's first see if we can find a weapon appropriate for the given tech type ...
	SET_OP;
	for (int weaponType = (int)scanStartVal; weaponType < (int) scanEndVal; ++weaponType) {
		const MWEquipment* stats = getWeaponStats((WeaponType)weaponType);
		if (BGCPString(stats->longName) == weaponName
		||  BGCPString(stats->shortName) == weaponName)
		{
			addWeapon(section, (WeaponType) weaponType);
			RETURN true;
		}
	}

	// If we can't, try again, this time with all weapons ...
	for (int weaponType = (int)Unused_Critical_Slot; weaponType < (int) WeaponsCount; ++weaponType) {
		const MWEquipment* stats = getWeaponStats((WeaponType)weaponType);
		if ((stats != nullptr) 
		&& (BGCPString(stats->longName) == weaponName ||  BGCPString(stats->shortName) == weaponName))
		{
			addWeapon(section, (WeaponType) weaponType);
			RETURN true;
		}
	}

	RETURN false;
}

// Check if this vehicle differentiates between front and armor for a section
bool Vehicle::separateFrontAndRearArmor(int section) const
{
	return false;
}

// Query damage to armor and internal structure
double Vehicle::getArmorDamage(int section, int armorType) const
{
	return 0.0;
}

double Vehicle::getInternalDamage(int section) const
{
	return 0.0;
}

// Check if the critical in section is damaged
bool Vehicle::criticalDamaged(int section, int critical) const
{
	return false;
}

// Set direction where the vehicle is aiming, by moving the turret (vehicles) or twisting and tilting the torso (mechs)
void Vehicle::setAim(double turn, double tilt)
{
}

WeaponType Vehicle::getHeatSinkType() const
{
	return HeatSinkType;
}

void Vehicle::setHeatSinkType(WeaponType newType)
{
	HeatSinkType = newType;
}

WeaponType Vehicle::getInternalStructureType() const
{
	return InternalStructureType;
}

void Vehicle::setInternalStructureType(WeaponType newType)
{
	InternalStructureType = newType;
}

// Get tilt and turn for current aim direction
double Vehicle::getAimTilt() const
{
	return 0.0;
}

double Vehicle::getAimTurn() const
{
	return 0.0;
}

// Move aim towards given angles by at most delta
void Vehicle::adjustAim(double desiredTorsoTwist, double desiredTorsoTilt, double delta)
{
}

const MountedWeapon* Vehicle::getActiveWeapon() const
{
	SET_SCOPE;
	if (mActiveWeapon < (int) weapons.size() && mActiveWeapon >= 0) {
		SET_OP;
		RETURN &weapons[mActiveWeapon];
	} else {
		RETURN nullptr;
	}
}

// Get max armor allowed for a section
int Vehicle::getMaxArmor(int section)
{
	return 60;
}

// Get texture used for the cockpit
Mesh* Vehicle::getCockpitMesh()
{
	return this;
}

// Check if the vehicle's cockpit has a reticle that moves horizontally
bool Vehicle::reticleMovesHorizontally() const
{
	return true;
}

bool Vehicle::reticleMovesVertically() const
{
	return false;
}

// Is the game object destroyed?
bool Vehicle::isDestroyed() const
{
	if (vehicleStatus == destroyed) {
		return true;
	} else {
		return false;
	}
}

// Query double data
double Vehicle::getDouble(int info) const
{
	SET_SCOPE;
	int tonnage = getInteger(Info_Tonnage);
	int engine = getInteger(Info_EngineRating);

	if (info == Info_WalkingHexes) {
		if (tonnage > 0) {
			RETURN ((double)engine) / tonnage;
		} else {
			RETURN 0; // Missing config?
		}
	} else {
		if (tonnage > 0) {
			RETURN 1.5 * engine / tonnage;
		} else {
			RETURN 0; // Missing config?
		}
	}
}

// check if o belongs to a vehicle and if so, return it, or null otherwise
Vehicle* getVehicle(RenderableObject* o)
{
	SET_SCOPE;
	while (o != nullptr) {
		Vehicle* v = dynamic_cast<Vehicle*>(o);
		if (v != nullptr) {
			RETURN v;
		} else {
			o = o->getParent();
		}
	}

	RETURN nullptr;
}

// Assuming that groundPoly intersects with the Mech/vehicle, calculate how far the Mech/vehicle should be moved up to resolve the collision
double Vehicle::calcMoveUp(const MeshPolygon* groundPoly, const Point3D& movedP) const
{
	SET_SCOPE;
	if (groundPoly == nullptr) {
		RETURN 0;
	}

	SET_OP;
	Plane groundPolyPlane;
	if (!groundPoly->getPlane(groundPolyPlane)) {
		RETURN 0.0;
	}

	SET_OP;
	double result = 0.0;
	double val = -groundPolyPlane.halfSpaceTest(movedP);
	if (val < result) {
		result = val;
	}

	RETURN -result;
}

int Vehicle::autoAddCriticals(WeaponType weaponType, int count, int /*critsPerUnit*/)
{
	SET_SCOPE;
	int added = 0;
	int section = (int)getSectionCount() - 1;

	while (section >= 0 && added < count) {
		int addedNow = addWeapon(section, weaponType);
		if (addedNow > 0) {
			added += addedNow;
		} else {
			--section;
		}
	}

	RETURN added;
}

// Check if the vehicle has ground contact
bool Vehicle::hasGroundContact() const
{
	return mHasGroundContact;
}

// Set/clear flag indicating whether the vehicle has ground contact
void Vehicle::setHasGroundContact(bool newVal)
{
	mHasGroundContact = newVal;
}

// How many degrees does the vehicle turn per second?
double Vehicle::getMaxTurnPerSecond() const
{
	return mMaxTurnPerSecond;
}

void Vehicle::nextWeapon()
{
	SET_SCOPE;
	if (weapons.size() > 0) {
		mActiveWeapon = (mActiveWeapon + 1) % weapons.size();
	}
	RETURN;
}

double Vehicle::calcInternalStructureWeight() const
{
	return tonnage / 10.0;
}

// Access current vehicle speed
void Vehicle::setSpeed(double newVal)
{
	mSpeed = putInRange(mMinSpeed, newVal, mMaxSpeed);
}

double Vehicle::getSpeed() const
{
	return mSpeed * mMobility;
}

// After loading a config, use this function to set min/max speeds using vehicle-type-specific rules
void Vehicle::setMinMaxSpeed()
{
	mMinSpeed = -2.0 * mMetersPerSecond / 3.0;
	mMaxSpeed = mMetersPerSecond;
}

void Vehicle::addMovement(const Point3D& newVal)
{
	mMovement += newVal; 
}

// Check how many jump jets are active
int Vehicle::getActiveJumpJets() const
{
	return (int) (fabs(mJumpJetsActive.x) + fabs(mJumpJetsActive.y) + fabs(mJumpJetsActive.z));
}

// Deactivate jump jets, e.g. if fuel is used up
void Vehicle::deactivateJumpJets()
{
	mJumpJetsActive = Point3D(0, 0, 0);
}

// Get speed percent - used for showing the speed bar in the instrument panel
double Vehicle::getSpeedPercent() const
{
	return 100.0 * getSpeed() / getMaxSpeed();
}

// Access weapon list
size_t Vehicle::getWeaponListEntryCount() const
{
	return weapons.size();
}

const char* Vehicle::getWeaponListEntryName(size_t entry) const
{
	if (entry < weapons.size()) {
		WeaponType weaponType = weapons[entry].weaponType;
		const MWEquipment* weapon = getWeaponStats(weaponType);
		if (weapon != nullptr) {
			return weapon->shortName;
		}
	}
	return nullptr;
}

// Access vehicle status
VehicleStatus Vehicle::getStatus() const
{
	return vehicleStatus;
}

void Vehicle::setStatus(VehicleStatus newStatus)
{
	vehicleStatus = newStatus;
	if (newStatus == VehicleStatus::destroyed) {
		MechSim* mechSim = getMechSim();
		mechSim->updateObjectStatus(mID, SubObjectiveType::attack, false);
		mDescriptionText.print();
		COUT << " , piloted by ";
		mInspectionText.print();
		auto ticks = mechSim->missionTime();
		COUT << ", destroyed after " << ticks / 1000 << " seconds" << std:: endl;
		setColorRec(Color(0.0, 0.0, 0.0));
	}
}

// Get vehicle AI
AbstractVehicleAI* Vehicle::getAI() 
{
	return mAI;
}

// Get/set Mech comment
const BGCPString& Vehicle::getComment() const
{
	return comment;
}
void Vehicle::setComment(const BGCPString& newComment)
{
	comment = newComment;
}

// The point to attack may differ per vehicle class, the center of the volume isn't necessarily a good spot to attack
Point3D Vehicle::getAttackSpot(bool onGround) const
{
	return getPosition();
}

// Get range of currently active weapon
double Vehicle::getActiveWeaponRange () const
{
	SET_SCOPE;
	const MountedWeapon* weapon = getActiveWeapon();
	if (weapon == nullptr) {
		RETURN 0.0;
	}
	const MWEquipment* weaponStats = getWeaponStats(weapon->weaponType);
	if (weaponStats == nullptr) {
		RETURN 0.0;
	}

	RETURN weaponStats->MaxRangeInMeters;
}

// Get sensor damage - 0 equals no damage, 1 equals sensors completely destroyed
double Vehicle::getSensorDamage()
{
	return 0.0;
}

// Given an MVMK and config file, determine the market price of that vehicle in C-Bills
// using BMR, p.149. Only BattleMech cost is implemented yet, other vehicle types probably need additional code
int calcVehiclePrice(const BGCPString& mvmkPath, const BGCPString& configPath)
{
	// Load vehicle
	SET_SCOPE;
	Vehicle* vehicle = loadVehicle2(mvmkPath, configPath, "", "");
	if (vehicle == nullptr) {
		RETURN 0;
	}

	// Count criticals
	SET_OP;
	std::vector<int> criticalCount(WeaponsCount);
	for (int section = 0; section < vehicle->getSectionCount(); ++section) {
		for (int crit = 0; crit < vehicle->getCriticalCount(section); ++crit) {
			auto critType = vehicle->getCriticals(section, crit);
			++criticalCount[critType];
		}
	}

	// Calcalute price of criticals
	int equipmentPrice = 0;
	for (size_t i = 0; i < WeaponsCount; ++i) {
		const MWEquipment* weaponStats = getWeaponStats((WeaponType)i);
		int installedCount = criticalCount[i];
		if (installedCount > 0 && weaponStats != nullptr) {
			if (weaponStats->Criticals > 0) {
				installedCount /= weaponStats->Criticals;
			}
			if (installedCount > 0 && weaponStats->CBills > 0) {
				equipmentPrice += weaponStats->CBills * installedCount;
			}
		}
	}

	// Structural costs factor in tonnage
	int tonnage = vehicle->getInteger(Vehicle::Info_Tonnage);

	int structuralCosts = 0;
	structuralCosts += tonnage * 2000; // sensors
	structuralCosts += tonnage * 2000; // musculature
	int armActuatorsPrice = criticalCount[CE_Upper_Arm_Actuator] * tonnage * 100;
	armActuatorsPrice += criticalCount[CE_Lower_Arm_Actuator] * tonnage * 50;
	armActuatorsPrice += criticalCount[CE_Hand] *  tonnage * 80;
	int legActuatorsPrice = criticalCount[CE_Upper_Leg_Actuator] * tonnage * 150;
	legActuatorsPrice += criticalCount[CE_Lower_Leg_Actuator] * tonnage * 80;
	legActuatorsPrice += criticalCount[CE_Foot] * tonnage * 120;
	structuralCosts += armActuatorsPrice + legActuatorsPrice;

	// Internal structure
	int internalStructurePrice = 0;
	if (criticalCount[IS_Endo_Steel] > 0 || criticalCount[Clan_Endo_Steel_Internals] > 0) {
		internalStructurePrice += tonnage * 1600;
	} else {
		internalStructurePrice += tonnage * 400;
	}

	// Engine
	int engineType = vehicle->getInteger(Vehicle::Info_EngineType);
	int engineRating = vehicle->getInteger(Vehicle::Info_EngineRating);
	int enginePrice = 0;
	if (engineType == ENGINE_TYPE_STD) {
		enginePrice += 5000 * engineRating * tonnage / 75;
	} else {
		enginePrice += 20000 * engineRating * tonnage / 75;
	}

	// Gyro
	int gyroWeight = (engineRating + 95) / 100;
	int gyroPrice = 300000 * gyroWeight;

	// Jump Jets
	int jjCount = criticalCount[CE_Jump_Jet];
	int jjPrice = tonnage * jjCount * jjCount * 200;

	// Heat sinks
	int heatSinkCount = vehicle->getInteger(Vehicle::Info_Heat_Sinks);
	int heatSinkType = vehicle->getInteger(Vehicle::Info_Heat_Sink_Type);
	int hsPrice = 0;
	if (heatSinkType == IS_Heat_Sink) {
		if (heatSinkCount > 10) {
			hsPrice = 2000 * (heatSinkCount - 10);
		}
	} else {
		hsPrice = 6000 * heatSinkCount;
	}

	// Armor
	int armorType = vehicle->getInteger(Vehicle::Info_Armor_Type);

	int armorPoints = 0;
	for (int section = 0; section < vehicle->getSectionCount(); ++section) {
		armorPoints += vehicle->getArmor(section, Vehicle::Armor_Front) + vehicle->getArmor(section, Vehicle::Armor_Rear);
	}

	double armorWeight = vehicle->calcArmorWeight();
	int armorPrice = 0;
	if (armorType == ARMOR_TYPE_STD) {
		armorPrice = (int)(10000 * armorWeight);
	} else {
		armorPrice = (int)(20000 * armorWeight);
	}

	// Final multiplier
	int price = equipmentPrice + structuralCosts + internalStructurePrice + enginePrice + gyroPrice + jjPrice + hsPrice + armorPrice;
	price = (int)(price * (1.0 + tonnage / 100.0));

	RETURN price;
}

// Provide a vehicle summary for display
void Vehicle::getSummary(BGCPString& str)
{
	SET_SCOPE;
	str = BGCPString("\"") + comment + BGCPString("\"\n");

	double realWeight = calcRealWeight();
	str += BGCPString::fromDouble(realWeight,2) + BGCPString(" T/") + BGCPString::fromInt(tonnage) + BGCPString(" T\n");

	double armorWeight = calcArmorWeight();
	str += BGCPString("Protected by ") + BGCPString::fromDouble(armorWeight) + BGCPString(" T of armor\n");

	int jjs = getInteger(Info_JumpJets);
	if (jjs > 0) {
		str += BGCPString::fromInt(jjs) + BGCPString(" jump jets\n");
	}

	#ifdef MECHVM
	int heatSinks = getInteger(Info_Heat_Sinks);
	int heatSinkType = getInteger(Info_Heat_Sink_Type);
	if (heatSinkType == IS_Heat_Sink) {
		str += BGCPString("Heat Sinks: ") + BGCPString::fromInt(heatSinks) + BGCPString("\n");
	} else {
		str += BGCPString("Heat Sinks: ") + BGCPString::fromInt(heatSinks) + BGCPString(" (") + BGCPString::fromInt(2*heatSinks) + 
			BGCPString(")\n");
	}
	#endif

	// Count criticals
	SET_OP;
	std::vector<int> criticalCount(WeaponsCount);
	for (int section = 0; section < getSectionCount(); ++section) {
		for (int crit = 0; crit < getCriticalCount(section); ++crit) {
			auto critType = getCriticals(section, crit);
			++criticalCount[critType];
		}
	}

	// Report weapon systems
	int equipmentPrice = 0;
	for (size_t i = 0; i < WeaponsCount; ++i) {
		if (i < Common_Equipment 
		|| (i > Clan_Energy_Weapon_Base && i < Clan_Equipment))
		{
			const MWEquipment* weaponStats = getWeaponStats((WeaponType)i);
			if (weaponStats != nullptr && criticalCount[i] > 0) {
				str += BGCPString::fromInt(criticalCount[i] / weaponStats->Criticals) + BGCPString(" ") + BGCPString(weaponStats->longName) + 
					BGCPString("\n");
			}
		}
	}

	RETURN;
}

// Construct transform matrix for a limb and assign it to the limb
Matrix Vehicle::setLimbMatrix(int limb, const Matrix& baseMatrix, double angle, 
	float axis_x, float axis_y, float axis_z)
{
	SET_SCOPE;
	auto* obj = getObject(limb);
	Matrix result = baseMatrix * TranslationMatrix(m_joint[limb]);
	result = result * RotationMatrix(M_PI * angle / 180.0, axis_x, axis_y, axis_z);
	result = result * TranslationMatrix(-m_joint[limb]);
	setMatrixIfNN(obj, result);
	RETURN result;
}