////////////////////////////////////////////////////////////////////////////////
// QuadMech.cpp
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

// Quad Mechs are also referred to as four-legged Mechs in the BattleTech
// Master Rules, where they are described in Special Case Rules, 
// p. 75 (v1), p. 82 (v2)

#include "GroundVehicleAI.h"
#include "MechSim.h"
#include "PlayerGroundVehicle.h"
#include "QuadMech.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_QUADMECH_CPP

////////////////////////////////////////////////////////////////////////////////
// Mech section identifiers

const int Quad_Mech_Section_Head = 0;
const int Quad_Mech_Section_Center_Torso = 1;
const int Quad_Mech_Section_Left_Torso = 2;
const int Quad_Mech_Section_Right_Torso = 3;
const int Quad_Mech_Section_Left_Front_Leg = 4;
const int Quad_Mech_Section_Right_Front_Leg = 5;
const int Quad_Mech_Section_Left_Rear_Leg = 6;
const int Quad_Mech_Section_Right_Rear_Leg = 7;

const int MaxQuadCriticalsPerSection = 12;

////////////////////////////////////////////////////////////////////////////////
// Mech mesh identifiers

const int Quad_Mech_Part_Hip = 0;
const int Quad_Mech_Part_Torso = 1;
const int Quad_Mech_Part_FrontLeftUpperLeg = 2;
const int Quad_Mech_Part_FrontLeftLowerLeg = 3;
const int Quad_Mech_Part_FrontLeftFoot = 4;
const int Quad_Mech_Part_FrontRightUpperLeg = 5;
const int Quad_Mech_Part_FrontRightLowerLeg = 6;
const int Quad_Mech_Part_FrontRightFoot = 7;
const int Quad_Mech_Part_RearLeftUpperLeg = 8;
const int Quad_Mech_Part_RearLeftLowerLeg = 9;
const int Quad_Mech_Part_RearLeftFoot = 10;
const int Quad_Mech_Part_RearRightUpperLeg = 11;
const int Quad_Mech_Part_RearRightLowerLeg = 12;
const int Quad_Mech_Part_RearRightFoot = 13;
const int Quad_Mech_Part_Windshield = 14;
const int Quad_Mech_Part_LeftLowerArm = 15;
const int Quad_Mech_Part_RightLowerArm = 16;
const int Quad_Mech_Part_Cockpit = 17;

const size_t Quad_Mech_MAX_PARTS = 18;

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

QuadMech::QuadMech()
: Vehicle("QuadMech", QuadMechID, nullptr, Quad_Mech_MAX_PARTS, "H T L   "),
  torsoTwistLimit(90.0),
  torsoTiltLimit(30.0)
{
	SET_SCOPE;
	mTorsoTilt = 0;
	mTorsoTwist = 0;
	mSpeed = 0;
	mSections.resize(Quad_Mech_Sections);
	mArmorDamageFront.resize(Quad_Mech_Sections);
	mArmorDamageRear.resize(Quad_Mech_Sections);
	mInternalDamage.resize(Quad_Mech_Sections);

	const int QuadCriticalCount[Quad_Mech_Sections] = 
		{6, 12, 12, 12, 6, 6, 6, 6};

	for (int i = 0; i < Quad_Mech_Sections; i++) {
		SET_OP;
		mFrontArmor[i] = 0;
		mRearArmor[i] = 0;
		mInternalStructure[i] = 0;

		VehicleSection& section = mSections[i];
		if (i < 4) {
			section.sectionName = tr (i+4);
		} else {
			section.sectionName = tr (i+34);
		}

		auto thisSectionCriticals = QuadCriticalCount[i];
		section.criticals.insert(section.criticals.begin(), thisSectionCriticals, WeaponType::Unused_Critical_Slot);
	}

	SET_OP;
	mCriticalDamaged.resize(Quad_Mech_Sections);
	for (int i = 0; i < Quad_Mech_Sections; i++) {
		for (int j = 0; j < getCriticalCount(i); j++) {
			mCriticalDamaged[i].push_back(false);
		}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

QuadMech::~QuadMech()
{
}

////////////////////////////////////////////////////////////////////////////////
// Query/set data

int QuadMech::getInteger (int info) const
{
   return Vehicle::getInteger(info);
}

bool QuadMech::setInteger (int info, int newVal)
{
   return Vehicle::setInteger(info, newVal);
}

////////////////////////////////////////////////////////////////////////////////
// Add weapon

bool QuadMech::addWeapon(int currSection, WeaponType weaponNo)
{
   const MWEquipment* weapon = getWeaponStats(weaponNo);

   if (weapon == NULL)
      return false;

   int freeCriticals = countWeaponCriticals (currSection, WeaponType::Unused_Critical_Slot);

   if (weaponNo == WeaponType::IS_Heat_Sink
   ||  weaponNo == WeaponType::IS_Double_Heat_Sink
   ||  weaponNo == WeaponType::Clan_Double_Heat_Sink)
   {
      if (weaponNo != HeatSinkType) {
         COUT << "Incompatible heat sink type" << std::endl;
         return false;
      }

      if (heatSinks < engineRating / 25) {
         // Heat sink that does not need critical space, add and abort
         ++heatSinks;
         COUT << "Internal heat sink added - " << heatSinks << std::endl;
         return true;
      } else {
         if (freeCriticals >= weapon->Criticals) {
            // Heat sink that needs normal critical space, treated normally
            ++heatSinks;
            COUT << "External heat sink added - " << heatSinks << std::endl;
         } else
            // Heat sink needs critical space, but no criticals available
            return false;
      }
   }

   // Add criticals
   if (freeCriticals < weapon->Criticals)
      return false;

   MountedWeapon newWeapon;
   if ((weapon->flags & MWWeaponFlag_IncludeInWeaponList) > 0) {
       newWeapon.weaponType = weaponNo;
       newWeapon.group = 0;
       newWeapon.criticals.clear();
   }

   int added = 0;
   int i = 0;
   int max = getCriticalCount(currSection);

   while (i < max && added < weapon->Criticals) {
      if (getCriticals(currSection, i) == WeaponType::Unused_Critical_Slot) {
         setCriticals(currSection, i, weaponNo);

         newWeapon.section = currSection;
         newWeapon.criticals.push_back(i);

         ++added;
      } 
      ++i;
   }

   weapons.push_back(newWeapon);
   return true;
}

////////////////////////////////////////////////////////////////////////////////
// Remove weapon - returns number of criticals removed

int QuadMech::removeWeapon(int currSection, int critNo, WeaponType weaponNo, 
                           int critCount)
{
	SET_SCOPE;
   if (weaponNo == WeaponType::IS_Heat_Sink
   ||  weaponNo == WeaponType::IS_Double_Heat_Sink
   ||  weaponNo == WeaponType::Clan_Double_Heat_Sink)
   {
      --heatSinks;
      COUT << "External heat sink removed" << std::endl;
   }

   int removed = 0;
   int i = critNo;
   int max = getCriticalCount(currSection);

   if (critCount == -1) {
      const MWEquipment* weapon = getWeaponStats(weaponNo);
      if (weapon == NULL) {
         RETURN 0;
		}
      critCount = weapon->Criticals;
   }

   int oneCrit = -1;
   while (removed < critCount 
   &&     i < 2*max) // test against twice crit count if tests start in last critical
   {
      if (getCriticals(currSection, i%max) == weaponNo) {
         setCriticals(currSection, i%max, WeaponType::Unused_Critical_Slot);
         oneCrit = i%max;
         ++removed;
      }
      ++i;
   }

   // Check and update weapon systems table
   if (removed > 0 && oneCrit >= 0) {
      auto it = weapons.begin();
      while (it < weapons.end()) {
			auto& weapon = *it;
         size_t j = 0;
         while (j < weapon.criticals.size()) {
             if (weapon.section == currSection 
             &&  weapon.criticals[j] == oneCrit)
             {
                 it = weapons.erase(it);
					RETURN removed;
             } else
                 ++j;
         }
         ++it;
      }
   }

	RETURN removed;
}

////////////////////////////////////////////////////////////////////////////////
// Check - and try to correct - config problems 
// Returns 0 on success, and the number of a warning for the tr() function otherwise

int QuadMech::checkAndFixEngineCrits(int section)
{
   int engineCritsCurrentlyInSection = countWeaponCriticals (section, WeaponType::CE_Engine);
   int reqSideTorsoSlots = 3;
   if (techType == Tech_Type_Clan)
      reqSideTorsoSlots = 2;

   if (engineCritsCurrentlyInSection == reqSideTorsoSlots) 
      return 0;

   int unusedCritsInSection = countWeaponCriticals (section, WeaponType::Unused_Critical_Slot);
   if (reqSideTorsoSlots - engineCritsCurrentlyInSection < unusedCritsInSection) {
      // Add missing side torso engine slots
      while (engineCritsCurrentlyInSection < reqSideTorsoSlots) {
         addWeapon (section, WeaponType::CE_Engine);
         ++engineCritsCurrentlyInSection;
      }
      return 0;
   } else {
      // Can't add missing side torso slots because there not enough free criticals
      return 27;
   }
}

void QuadMech::checkCritsInSection(WeaponType equipmentType, int countNeeded, int section)
{
      int equipmentCrits = countWeaponCriticals(section, equipmentType);
      if (equipmentCrits < countNeeded) {
         int unused = countWeaponCriticals(section, WeaponType::Unused_Critical_Slot);
         if (unused > 0) {
            addWeapon(section, equipmentType);
         }
      }
}

// Returns true on success, false otherwise, and sets an error description on failure
bool QuadMech::check(BGCPString& errors)
{
	// Overweight?
	SET_SCOPE;
	if (getWeight() > tonnage) {
		errors = tr(26);
		RETURN false;
	}

	// XL engine criticals in side torsi?
	if (getInteger(Info_EngineType) == ENGINE_TYPE_XL) {
		int engRes = checkAndFixEngineCrits(Quad_Mech_Section_Left_Torso);
		if (engRes > 0) {
			errors = tr(engRes);
			RETURN false;
		}

		engRes = checkAndFixEngineCrits(Quad_Mech_Section_Right_Torso);
		if (engRes > 0) {
			errors = tr(engRes);
			RETURN false;
		}
	}

	// Check for correct number of endo steel internals
	int endoCritsIS = countWeaponInAllSections(WeaponType::IS_Endo_Steel);
	int endoCritsClan = countWeaponInAllSections(WeaponType::Clan_Endo_Steel_Internals);
	if ((endoCritsIS != 0   && endoCritsIS != 14)
	||  (endoCritsClan != 0 && endoCritsClan != 7))
	{
		errors = tr(36);
		RETURN false;
	}

	// Check for correct number of endo steel internals
	int ffCritsIS = countWeaponInAllSections(WeaponType::IS_Ferro_Fibrous);
	int ffCritsClan = countWeaponInAllSections(WeaponType::Clan_Ferro_Fibrous);
	if ((ffCritsIS != 0   && ffCritsIS != 14)
	||  (ffCritsClan != 0 && ffCritsClan != 7))
	{
		errors = tr(37);
		RETURN false;
	}

	// Check if the jump jets are less or equals to the walking movement rate
	int jjCrits = countWeaponInAllSections(WeaponType::CE_Jump_Jet);
	if (jjCrits > getInteger (Info_EngineRating) / tonnage)
	{
			errors = tr(43);
			RETURN false;
	}

	// Check leg criticals
	for (int section = Quad_Mech_Section_Left_Front_Leg; 
		section <= Quad_Mech_Section_Right_Rear_Leg;
		++section)
	{
		checkCritsInSection(WeaponType::CE_Hip, 1, section);
		checkCritsInSection(WeaponType::CE_Upper_Leg_Actuator, 1, section);
		checkCritsInSection(WeaponType::CE_Lower_Leg_Actuator, 1, section);
		checkCritsInSection(WeaponType::CE_Foot, 1, section);
	}

	// No errors found
	errors.clear();
	return 0;
}

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

double QuadMech::calcInternalStructureWeight() const
{
   if (InternalStructureType == WeaponType::Unused_Critical_Slot)
      return tonnage / 10.0;
   else
      return tonnage / 20.0;
}

double QuadMech::calcJJWeight () 
{
   jumpJets = countWeaponInAllSections(WeaponType::CE_Jump_Jet);

   if (tonnage > 85)
      return 2*jumpJets;
   else if (tonnage > 55)
      return jumpJets;
   else
      return jumpJets / 2.0;
}

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

int QuadMech::countArmorPoints() const
{
   int armorPoints = mRearArmor[Quad_Mech_Section_Center_Torso] +
                     mRearArmor[Quad_Mech_Section_Left_Torso] +
                     mRearArmor[Quad_Mech_Section_Right_Torso];
                
   for (int i = 0; i < 8; i++) 
      armorPoints += mFrontArmor[i];

   return armorPoints;
}

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

double QuadMech::calcRealWeight()
{
   // Add other equipment
   double equipmentTonnage = 0;
   for (int i = 0; i < Quad_Mech_Sections; i++) {
      for (int j = 0; j < getCriticalCount (i); j++) {
         WeaponType crit = getCriticals(i, j);
         const MWEquipment* weapon = getWeaponStats (crit);
         if (weapon != NULL)
            if (weapon->TonnageTimes100 > 0.0)
               equipmentTonnage += weapon->TonnageTimes100 / (100.0*weapon->Criticals);
      }
   }

   double engineWeight = getEngineWeight (engineRating, EngineType);
   double internalStructureWeight = calcInternalStructureWeight();
   double armorWeight = calcArmorWeight();
   double gyroWeight = (engineRating + 95) / 100;
   double HeatSinkWeight = std::max(heatSinks-10,0);
   double cockpitWeight = 3.0;
   double jjWeight = calcJJWeight();

   int MASCTonsAndSlots = calcMASCTonsAndSlots();
   int IS_MASC_slots = countWeaponInAllSections (WeaponType::IS_MASC);
   int Clan_MASC_slots = countWeaponInAllSections (WeaponType::Clan_MASC);

   if (IS_MASC_slots < MASCTonsAndSlots
   &&  Clan_MASC_slots < MASCTonsAndSlots)
   {
      MASCTonsAndSlots = 0;
   }

   realWeight = engineWeight + internalStructureWeight + gyroWeight +
                armorWeight + equipmentTonnage + cockpitWeight +
                HeatSinkWeight + jjWeight + MASCTonsAndSlots;

   return realWeight;
}

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

void QuadMech::readSpecialAttributesFromRoot(XMLTreeConstIterator& iter)
{
	torsoTwistLimit = iter.getDoubleAttributeByName("torsoTwistLimit", 90.0);
	torsoTiltLimit = iter.getDoubleAttributeByName("torsoTiltLimit", 30.0);
}

void QuadMech::readSpecialAttributes(XMLTreeConstIterator& iter, const std::vector<RenderableObject*>& geometries, const PathChar* mechDir)
{
	int partNumber = Quad_Mech_Part_Hip;
	bool LoadPart = true;

	if (iter.nodeNameEquals("hip")) {
		placePart(iter, geometries, Quad_Mech_Part_Hip, mechDir);
	} else if (iter.nodeNameEquals("torso")) {
		placePart(iter, geometries, Quad_Mech_Part_Torso, mechDir);
	} else if (iter.nodeNameEquals("LeftFrontUpperLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_FrontLeftUpperLeg, mechDir);
	} else if (iter.nodeNameEquals("LeftFrontLowerLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_FrontLeftLowerLeg, mechDir);
	}else if (iter.nodeNameEquals("RightFrontUpperLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_FrontRightUpperLeg, mechDir);
	} else if (iter.nodeNameEquals("RightFrontLowerLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_FrontRightLowerLeg, mechDir);
	} else if (iter.nodeNameEquals("LeftRearUpperLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_RearLeftUpperLeg, mechDir);
	} else if (iter.nodeNameEquals("LeftRearLowerLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_RearLeftLowerLeg, mechDir);
	} else if (iter.nodeNameEquals("LeftFoot")) {
		placePart(iter, geometries, Quad_Mech_Part_RearLeftFoot, mechDir);
	} else if (iter.nodeNameEquals("RightRearUpperLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_RearRightUpperLeg, mechDir);
	} else if (iter.nodeNameEquals("RightRearLowerLeg")) {
		placePart(iter, geometries, Quad_Mech_Part_RearRightLowerLeg, mechDir);
	} else if (iter.nodeNameEquals("RightFoot")) {
		placePart(iter, geometries, Quad_Mech_Part_RearRightFoot, mechDir);
	} else if (iter.nodeNameEquals("windshield")) {
		placePart(iter, geometries, Quad_Mech_Part_Windshield, mechDir);
	} else if (iter.nodeNameEquals("cockpit")) {
		placePart(iter, geometries, Quad_Mech_Part_Cockpit, mechDir);
	} else if (iter.nodeNameEquals("anim")) {
		LoadPart = false;
		addAnim (iter);
	} else if (iter.nodeNameEquals("cockpit-coords")) {
		cockpitCoords.x = iter.getDoubleAttributeByName("x", cockpitCoords.x);
		cockpitCoords.y = iter.getDoubleAttributeByName("y", cockpitCoords.y);
		cockpitCoords.z = iter.getDoubleAttributeByName("z", cockpitCoords.z);
		LoadPart = false;
	} else if (iter.nodeNameEquals("critical-layout")) {
		LoadPart = false;
		criticalLayouts.resize(Quad_Mech_Sections);
		for (size_t k = 0; k < iter.getChildCount(); k++) {
			iter.goToChild(k);
			int location = -1;
			if (iter.nodeNameEquals("head")) {
				location = Quad_Mech_Section_Head;
			} else if (iter.nodeNameEquals("center-torso")) {
				location = Quad_Mech_Section_Center_Torso;
			} else if (iter.nodeNameEquals("left-torso")) {
				location = Quad_Mech_Section_Left_Torso;
			} else if (iter.nodeNameEquals("right-torso")) {
				location = Quad_Mech_Section_Right_Torso;
			}
			if (location >= 0 && location < Quad_Mech_Sections) {
				criticalLayouts[location].start.x = iter.getDoubleAttributeByName("sx");
				criticalLayouts[location].start.y = iter.getDoubleAttributeByName("sy");
				criticalLayouts[location].start.z = iter.getDoubleAttributeByName("sz");
				criticalLayouts[location].direction.x = iter.getDoubleAttributeByName("dx");
				criticalLayouts[location].direction.y = iter.getDoubleAttributeByName("dy");
				criticalLayouts[location].direction.z = iter.getDoubleAttributeByName("dz");
			}
			iter.goToParent();
		}
	} else if (iter.nodeNameEquals("cockpit-layout")) {
		LoadPart = false;
		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);
			}
			iter.goToParent();
		}
	} else
		LoadPart = false;
}

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

DWORD QuadMech::getArmor(int section, int ArmorType) const
{
   if (section < Quad_Mech_Sections) {
      if (ArmorType == Vehicle::Armor_Front)
         return mFrontArmor[section];
      else if (ArmorType == Vehicle::Armor_Rear)
         return mRearArmor[section];
   }

   return 0;
}

bool QuadMech::setArmor(int section, DWORD newVal, int ArmorType)
{ 
   bool maySet = false;

   if (ArmorType == Armor_Front) {
      if (section == Quad_Mech_Section_Center_Torso
      ||  section ==  Quad_Mech_Section_Left_Torso
      ||  section ==  Quad_Mech_Section_Right_Torso)
      {
         if ((int) newVal + mRearArmor[section] <= 2* mInternalStructure[section])
            maySet = true;
      } else if (section == Quad_Mech_Section_Head) {
         if (newVal < 10)
            maySet = true;
      } else {
         if ((int) newVal <= 2* mInternalStructure[section])
            maySet = true;
      }
   } else {
      if (section == Quad_Mech_Section_Center_Torso
      ||  section ==  Quad_Mech_Section_Left_Torso
      ||  section ==  Quad_Mech_Section_Right_Torso)
      {
         if ((int) newVal + mFrontArmor[section] <= 2* mInternalStructure[section])
            maySet = true;
      }
   }

   if (maySet) {
      if (ArmorType == Armor_Front) {
         mFrontArmor[section] = newVal;
      } else {
         mRearArmor[section] = newVal;
      }
      return true;
   } else
      return false;
}

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

void QuadMech::setInternalStructure (int section, DWORD newVal)
{
   if (section < Quad_Mech_Sections) {
      mInternalStructure[section] = newVal;
   }
}

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

/*void QuadMech::setCriticals (int section, int slot, WeaponType weaponType)
{
   if (section < Quad_Mech_Sections) {
      criticals[section][slot] = weaponType;
   }
}*/

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

double QuadMech::animate(int part, double phase, double speed, double phaseShift)
{
    if (allAnimations.size() == 0) {
        return 0.0;
    }

    // Find the nearest matching faster and slower animations
    int fasterAnimationIndex = -1;
    int slowerAnimationIndex = -1;

    for (size_t i = 0; i < allAnimations.size(); ++i) {
        if (allAnimations[i].metersPerSecond >= speed
            && (fasterAnimationIndex  < 0 || allAnimations[fasterAnimationIndex].metersPerSecond > allAnimations[i].metersPerSecond))
        {
            fasterAnimationIndex = i;
        }
        if (allAnimations[i].metersPerSecond <= speed
            && (slowerAnimationIndex < 0 || allAnimations[slowerAnimationIndex].metersPerSecond < allAnimations[i].metersPerSecond))
        {
            slowerAnimationIndex = i;
        }
    }

    if (fasterAnimationIndex >= 0 && slowerAnimationIndex >= 0 && fasterAnimationIndex != slowerAnimationIndex) {
        // Blend faster and slower animation
        double fastPhase = phase + phaseShift * allAnimations[fasterAnimationIndex].animDuration;
        double slowPhase = phase + phaseShift * allAnimations[slowerAnimationIndex].animDuration;
        double fastValue = animate_old(part, allAnimations[fasterAnimationIndex], fastPhase);
        double slowValue = animate_old(part, allAnimations[slowerAnimationIndex], slowPhase);
        double x = (speed - allAnimations[slowerAnimationIndex].metersPerSecond)
            / (allAnimations[fasterAnimationIndex].metersPerSecond - allAnimations[slowerAnimationIndex].metersPerSecond);
        return slowValue + x * (fastValue - slowValue);
    }
    else {
        // Use the one available animation
        int theChosenOne = std::max(std::max(fasterAnimationIndex, slowerAnimationIndex), 0);
        phase += phaseShift * allAnimations[theChosenOne].animDuration;
        return animate_old(part, allAnimations[theChosenOne], phase);
    }
}

////////////////////////////////////////////////////////////////////////////////
// Set current animation matrices

void QuadMech::setMatrices(double speed, DWORD missionTime)
{
	SET_SCOPE;
	Point3D pos = getPosition();
	Matrix M = TranslationMatrix(pos.x, pos.y, pos.z) * Matrix(p_left, p_up, p_front);

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

	SET_OP;
	Matrix hipMatrix = setLimbMatrix(Quad_Mech_Part_Hip, M, mTorsoTilt, 1, 0, 0);
	Matrix torsoMatrix = setLimbMatrix(Quad_Mech_Part_Torso, hipMatrix, mTorsoTwist, 0, 1, 0);
	setMatrixIfNN(getObject(Quad_Mech_Part_Cockpit), torsoMatrix);
	setMatrixIfNN(getObject(Quad_Mech_Part_Windshield), torsoMatrix);

	Matrix frontLeftLegMatrix(M);
	const double time = missionTime / 1000.0;
	double upperLegAngle = animate(1, time, speed, 0.0);
	frontLeftLegMatrix = frontLeftLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_FrontLeftUpperLeg]);
	frontLeftLegMatrix = frontLeftLegMatrix * RotationMatrix(M_PI * upperLegAngle / 180.0, 1, 0, 0);
	frontLeftLegMatrix = frontLeftLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_FrontLeftUpperLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_FrontLeftUpperLeg), frontLeftLegMatrix);

	double lowerLegAngle = animate(2, time, speed, 0.0);
	frontLeftLegMatrix = frontLeftLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_FrontLeftLowerLeg]);
	frontLeftLegMatrix = frontLeftLegMatrix * RotationMatrix(M_PI * lowerLegAngle / 180.0, 1, 0, 0);
	frontLeftLegMatrix = frontLeftLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_FrontLeftLowerLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_FrontLeftLowerLeg), frontLeftLegMatrix);

	Matrix frontRightLegMatrix(M);
	upperLegAngle = animate(1, time, speed, 0.5);
	frontRightLegMatrix = frontRightLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_FrontRightUpperLeg]);
	frontRightLegMatrix = frontRightLegMatrix * RotationMatrix(M_PI * upperLegAngle / 180.0, 1, 0, 0);
	frontRightLegMatrix = frontRightLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_FrontRightUpperLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_FrontRightUpperLeg), frontRightLegMatrix);

	lowerLegAngle = animate(2, time, speed, 0.5);
	frontRightLegMatrix = frontRightLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_FrontRightLowerLeg]);
	frontRightLegMatrix = frontRightLegMatrix * RotationMatrix(M_PI * lowerLegAngle / 180.0, 1, 0, 0);
	frontRightLegMatrix = frontRightLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_FrontRightLowerLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_FrontRightLowerLeg), frontRightLegMatrix);

	Matrix rearLeftLegMatrix(M);
	upperLegAngle = animate(1, time, speed, 0.5);
	rearLeftLegMatrix = rearLeftLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_RearLeftUpperLeg]);
	rearLeftLegMatrix = rearLeftLegMatrix * RotationMatrix(M_PI * upperLegAngle / 180.0, 1, 0, 0);
	rearLeftLegMatrix = rearLeftLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_RearLeftUpperLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_RearLeftUpperLeg), rearLeftLegMatrix);

	lowerLegAngle = animate(2, time, speed, 0.5);
	rearLeftLegMatrix = rearLeftLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_RearLeftLowerLeg]);
	rearLeftLegMatrix = rearLeftLegMatrix * RotationMatrix(M_PI * lowerLegAngle / 180.0, 1, 0, 0);
	rearLeftLegMatrix = rearLeftLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_RearLeftLowerLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_RearLeftLowerLeg), rearLeftLegMatrix);

	Matrix rearRightLegMatrix(M);
	upperLegAngle = animate(1, time, speed, 0.0);
	rearRightLegMatrix = rearRightLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_RearRightUpperLeg]);
	rearRightLegMatrix = rearRightLegMatrix * RotationMatrix(M_PI * upperLegAngle / 180.0, 1, 0, 0);
	rearRightLegMatrix = rearRightLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_RearRightUpperLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_RearRightUpperLeg), rearRightLegMatrix);

	lowerLegAngle = animate(2, time, speed, 0.0);
	rearRightLegMatrix = rearRightLegMatrix * TranslationMatrix(m_joint[Quad_Mech_Part_RearRightLowerLeg]);
	rearRightLegMatrix = rearRightLegMatrix * RotationMatrix(M_PI * lowerLegAngle / 180.0, 1, 0, 0);
	rearRightLegMatrix = rearRightLegMatrix * TranslationMatrix(-m_joint[Quad_Mech_Part_RearRightLowerLeg]);
	setMatrixIfNN(getObject(Quad_Mech_Part_RearRightLowerLeg), rearRightLegMatrix);
	RETURN;
}

void QuadMech::setAim(double turn, double tilt)
{
    mTorsoTilt = torsoTiltLimit * tilt / 30.0;
    mTorsoTwist = torsoTwistLimit * turn / 90.0;
}

////////////////////////////////////////////////////////////////////////////////
// Add anim parameters

void QuadMech::addAnim (XMLTreeConstIterator& iter)
{
    MechAnimation animationData;
    animationData.animDuration = iter.getDoubleAttributeByName("duration");
    animationData.metersPerSecond = iter.getDoubleAttributeByName("metersPerSecond");

    for (size_t i = 0; i < iter.getChildCount(); ++i) {
        iter.goToChild(i);
        double phase = iter.getDoubleAttributeByName("phase");
        double ulegAngle = iter.getDoubleAttributeByName("ulegAngle");
        double llegAngle = iter.getDoubleAttributeByName("llegAngle");
        double footAngle = iter.getDoubleAttributeByName("footAngle");

        animationData.data.emplace_back(phase);
        animationData.data.emplace_back(ulegAngle);
        animationData.data.emplace_back(llegAngle);
        animationData.data.emplace_back(footAngle);
        iter.goToParent();
    }

    allAnimations.push_back(animationData);
}

// Get maximum speed in meters per second currently supported by the vehicle
double QuadMech::getMaxSpeed() const
{
	// One walking hex gives a speed of 30m in 10 seconds, or 3 meters per second
	return 3.0 * getDouble(Vehicle::Info_RunningHexes) * mMobility;
}

// Check if this vehicle differentiates between front and armor for a section
bool QuadMech::separateFrontAndRearArmor(int section) const
{
	if (section == Quad_Mech_Section_Center_Torso
	||  section == Quad_Mech_Section_Left_Torso
	||  section == Quad_Mech_Section_Right_Torso)
	{
		return true;
	} else {
		return false;
	}
}

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

bool QuadMech::userMayRemoveDirectly (WeaponType weaponNo)
{
	if (weaponNo == Unused_Critical_Slot
	||  weaponNo == CE_Engine
	||  weaponNo == CE_Gyro
	||  weaponNo == CE_Life_Support
	||  weaponNo == CE_Cockpit
	||  weaponNo == CE_Sensors
	||  weaponNo == CE_Hip
	||  weaponNo == CE_Upper_Leg_Actuator
	||  weaponNo == CE_Lower_Leg_Actuator
	||  weaponNo == CE_Foot)
	{
		return false;
	} else {
		return true;
	}
}

// Show or hide the vehicle's windshiled (cockpit view may require it to be hidden)
void QuadMech::windshieldVisible(bool newVal)
{
	RenderableObject* child = getObject(Quad_Mech_Part_Windshield);
	if (child != nullptr) {
		if (newVal) {
			child->clearFlag(RenderableObject::Flag_invisible);
		} else {
			child->setFlag(RenderableObject::Flag_invisible);
		}
	}
}

// Check if a section may mount jump jets
bool QuadMech::mayMountJJs (int section)
{
	return (section != Quad_Mech_Section_Head);
}

// Return the number of the part corresponding to a section
// used by updateLoadoutGeometry() to find the mesh corresponding to the mech section
int QuadMech::section2part(int section, bool /*allowLowerArms*/)
{
	switch (section) {
		case Quad_Mech_Section_Head:
		case Quad_Mech_Section_Center_Torso:
		case Quad_Mech_Section_Left_Torso:
		case Quad_Mech_Section_Right_Torso:
			return Quad_Mech_Part_Torso;
		case Quad_Mech_Section_Left_Front_Leg:
			return Quad_Mech_Part_FrontLeftUpperLeg;
		case Quad_Mech_Section_Right_Front_Leg:
			return Quad_Mech_Part_FrontRightUpperLeg;
		case Quad_Mech_Section_Left_Rear_Leg:
			return Quad_Mech_Part_RearLeftUpperLeg;
		case Quad_Mech_Section_Right_Rear_Leg:
		default:
			return Quad_Mech_Part_RearRightUpperLeg;
	}
}

// Return the section corresponding to a limb
int QuadMech::limb2section(int limb)
{
	switch (limb) {
	case Quad_Mech_Part_Hip:
	case Quad_Mech_Part_Torso:
		return Quad_Mech_Section_Center_Torso; // could also be left/right torso ...
	case Quad_Mech_Part_FrontLeftUpperLeg:
	case Quad_Mech_Part_FrontLeftLowerLeg:
		return Quad_Mech_Section_Left_Front_Leg;
	case Quad_Mech_Part_FrontRightUpperLeg:
	case Quad_Mech_Part_FrontRightLowerLeg:
		return Quad_Mech_Section_Right_Front_Leg;
	case Quad_Mech_Part_RearLeftUpperLeg:
	case Quad_Mech_Part_RearLeftLowerLeg:
		return Quad_Mech_Section_Left_Rear_Leg;
	case Quad_Mech_Part_RearRightUpperLeg:
	case Quad_Mech_Part_RearRightLowerLeg:
		return Quad_Mech_Section_Right_Rear_Leg;
	case Quad_Mech_Part_Windshield:
		return Quad_Mech_Section_Head;
	default:
		return -1; // unknown limb ...
	}
}

// Get max armor allowed for a section
int QuadMech::getMaxArmor(int section)
{
	if (section != Quad_Mech_Section_Head) {
		return 2 * getInternalStructure(section);
	} else {
		return 9;
	}
}

// Get mesh used for the cockpit
Mesh* QuadMech::getCockpitMesh()
{
	SET_SCOPE;
	Mesh* cockpitMesh = dynamic_cast<Mesh*>(getObject(Quad_Mech_Part_Cockpit));
	if (cockpitMesh == nullptr) {
		cockpitMesh = this;
	}
	RETURN cockpitMesh;
}

// Check for a collision with a polygon and handle it
void QuadMech::checkCollision(const MeshPolygon* poly, Point3D& movement) 
{
	// todo: If only two of the legs touch ground, check if the mech should be rotated as a result
	// If the legs touch ground, climbing may help, if the polygon is not too steep
	// If another body part touches ground, it's a collision
	// If only the two legs touch ground, the quad mech should rotate around some axe until at leat three legs touch ground

	if (poly == nullptr || poly->getVertexCount() < 3) {
		return;
	}

	SET_SCOPE;
	BoundingBox polyBB = poly->getTransformedBB();

	// Check what body parts collide and store a vector that would fix things for each body part
	bool touchesThisPoly = false;
	double moveUp = 0.0;
	if (polyBB.valid()) {
		for (size_t i = 0; i < Quad_Mech_MAX_PARTS; ++i) {
			RenderableObject* bodyPart = getObject(i);
			if (bodyPart != nullptr) {
				BoundingBox bodyPartBB = bodyPart->getTransformedBB();
				bodyPartBB.applyMovement(movement);
				if (bodyPartBB.valid()
				&& polyBB.intersects(bodyPartBB)) 
				{
					// Find the polygon(s) that cause the collision
					Mesh* bodyPartMesh = dynamic_cast<Mesh*>(bodyPart);
					for (size_t j = 0; j < bodyPartMesh->getPolygonCount(); ++j) {
						MeshPolygon* bodyPartPolygon = bodyPartMesh->getPolygon(j);
						if (poly->collides(bodyPartPolygon, movement)) {
							for (size_t k = 0; k < bodyPartPolygon->getVertexCount(); ++k) {
								int l = bodyPartPolygon->getVertexIndex(k);
								Point3D movedP = bodyPartMesh->getTransformedVertex(l) + movement;
								double moveUpHere = calcMoveUp(poly, movedP);
								if (moveUpHere > moveUp) {
									moveUp = moveUpHere;
									touchesThisPoly = true;
									mHasGroundContact = true;
								}
							}
						}
					}
				}
			}
		}
	}

	if (touchesThisPoly) {
		movement -= moveUp * poly->getTransformedNormal();
	}

	// We have a collision, which is caused by the vehicle moving towards a collision. We need to eliminate the components
	// of the movement vector that cause the collision. So, transform the movement vector into the 3d space defined by the 
	// polygon's plane and normal, then remove any contributions rectangular to the plane. ed above, should result in exactly that.
	SET_OP;
	Point3D polyNormal = poly->transformedNormal;
	double polyAngle = Point3D::angle(Point3D(0, 1, 0), polyNormal);
	double angleBetweenPolyAndMovement = Point3D::angle(polyNormal, movement);
	/*if (polyAngle > 50 && angleBetweenPolyAndMovement > 90) {
		// todo: check, it doesn't work
		//illegalMove = true;
		movement = Point3D(0, 0, 0);
	}*/

	// Remove any components from the movement vector that are headed against the polygon normal
	// todo: this doesn't work. E.g., test it in mission pink.
	// Maybe M needs to be its own inverse?
	/*Plane plane;
	if (touchesThisPoly && poly->getPlane(plane)) {
		Point3D dir1, dir2;

		if ((plane.n-Point3D(0, 1, 0)).length() < 0.001) {
			dir1 = Point3D(1,0,0);
			dir2 = Point3D(0,0,1);
		} else if ((plane.n-Point3D(0, -1, 0)).length() < 0.001) {
			dir1 = Point3D(-1,0,0);
			dir2 = Point3D(0,0,-1);
		} else {
			dir1 = plane.n.crossMultiply(Point3D(0,1,0));
			dir2 = plane.n.crossMultiply(dir1);
		}

		double m[16] = {dir1.x, plane.n.x, dir2.x, 0.0,
			            dir1.y, plane.n.y, dir2.y, 0.0,
						dir1.z, plane.n.z, dir2.z, 0.0,
						0.0, 0.0, 0.0, 1.0};

		Matrix M(m);
		Point3D transformedMovement = M.Transform(movement);
		if (transformedMovement.y < 0.0) {
			transformedMovement.y *= -0.3;
			movement = transformedMovement.x * dir1 + transformedMovement.y * plane.n + transformedMovement.z * dir2;
		}
	}*/
	RETURN;
}

// Based on the vehicle, connect the vehicle to a suitable AI
AbstractVehicleAI* QuadMech::createAI(bool isPlayer, int environment)
{
	if (isPlayer) {
		return new PlayerGroundVehicle(this);
	} else {
		return new GroundVehicleAI("GroundVehicleAI", this);
	}
}

// Calculate heat for next frame
void QuadMech::calcNextFrameHeat(DWORD now, double deltaT)
{
	double heatDelta = 0.0;

	// Add heat from walking/running
	if (hasGroundContact() && abs(getSpeed()) > 0.1) {
		double walkingHexes = getDouble(Vehicle::Info_WalkingHexes);
		if (getSpeed() > 3.0 * walkingHexes) {
			// Running causes 2 heat points in 10 seconds, or 0.2 per second
			heatDelta += 0.2 * deltaT;
		} else {
			// Walking causes 1 heat point in 10 seconds, or 0.1 per second
			heatDelta += 0.1 * deltaT;
		}
	}

	// Add heat from engine hits
	heatDelta += deltaT * 0.5 * getEngineHits();

	// todo: Add heat from jumping (min. 3, max is equal to number of hexes jumped)

	// Add heat from fired weapons
	size_t weaponsCount = getWeaponListEntryCount();
	for (size_t i = 0; i < weaponsCount; ++i) {
		double lastFired = getLastFireTime(i);
		const MWEquipment* baseWeapon = getBaseWeapon(i);
		if (lastFired > 0 && lastFired + 1000.0 * baseWeapon->fireDuration  > now) {
			heatDelta += deltaT * baseWeapon->Heat / baseWeapon->fireDuration;
		}
	}

	// Distract cooling from heat sinks
	// todo: factor in mHeatSinkCriticalHits
	int effectiveHeatSinks = getEffectiveHeatSinks();
	heatDelta -= 0.1 * effectiveHeatSinks * deltaT; // One heat sink gets rid of 1 point of heat per 10 seconds, so 0.1 points per second

	// todo: double effectiveness of heat sinks that are below water (max. of 6 additional heat points)
	
	mCurrentHeat = std::max(mCurrentHeat + heatDelta, 0.0);

	// todo: On high heat, the mech may shut down or ammo may explode
	// todo: high heat reduces speed
	// todo: MechWarrior may take damage if heat reaches or exceeds 15.0
}

// Get current heat value for this Mech
double QuadMech::getHeat() const NOEXCEPT
{
	return mCurrentHeat;
}

// Calculate movement for next frame
void QuadMech::calcNextFrameMovement(double deltaT, RenderableObject* scene, DWORD now, double gravity)
{
	SET_SCOPE;
	if (isDestroyed()) {
		setSpeed(0.0);
		mAcceleration = Point3D();
		m_turnDelta = 0.0;
	}

	// Update position
	bool movementChanged = false;
	Point3D oldPos = getPosition();
	Point3D thisFrameMovement = deltaT * mMovement;

	// Now check the new position against the scene and update the position, until all collisions have been resolved
	setHasGroundContact(false);
	int loops = 0;
	do {
		setMatrices(getSpeed(), now);
		Matrix m;
		cacheTransformedVertices(m);
		BoundingBox vehicleBB = getTransformedBB();
		vehicleBB.applyMovement(thisFrameMovement);

		// Mechs must not fall through the ground at zero no matter what
		if (vehicleBB.min().y < mLift) {
			oldPos.y += mLift - vehicleBB.min().y;
			setHasGroundContact(true);
			movementChanged = true;
			thisFrameMovement.y = 0.0;
			mMovement.y = 0.0;
		} else {
			movementChanged = false;
			std::vector<RenderableObject*> colliders;
			findColliders(vehicleBB, scene, this, colliders);
			for (size_t i = 0; i < colliders.size(); ++i) {
				RenderableObject* collider = colliders[i];
				if (collider != nullptr) {
					Vehicle* otherVehicle = ::getVehicle(collider);
					if (otherVehicle != nullptr) {
						resolveObjectCollision(this, otherVehicle);
					} else {
						resolveGroundCollision(this, collider, vehicleBB, thisFrameMovement, movementChanged);
					}
				}
			}
		}
		++loops;
	} while (movementChanged && loops < MAX_COLLISION_RESOLVE_LOOPS);

	if (loops > MAX_COLLISION_RESOLVE_LOOPS) {
		COUT << "Failed to resolve collisions for";
		MVMKFN.printLn();
		mMovement = Point3D(0, 0, 0);
	}

	if (mPosition.y < 0.1) {
		setHasGroundContact(true);
	}

	// Now we can update the position
	setPosition(oldPos + thisFrameMovement);
	setMatrices(getSpeed(), now);

	// A Mech's gyro can be used to rotate a mech even when the Mech does not have ground contact
	// (Quad mechs have a gyro, too. Ref. RS 3055U, p.15, for Tarantula)
	mHeading += m_turnDelta * deltaT;
	double degrees = M_PI * mHeading / 180.0;
	Matrix rot = RotationMatrix(degrees, 0.0, 1.0, 0.0);
	p_front = rot.Transform(Point3D(0, 0, 1));
	p_left = rot.Transform(Point3D(-1, 0, 0));

	int jjsActive = getActiveJumpJets();
	int jjs = getInteger(Vehicle::Info_JumpJets);
	if (jjsActive > 0) {
		if (mJumpJetFuel > 0.0) {
			double fuelForThisFrame = jjUseRate * jjsActive * deltaT;
			mJumpJetFuel -= fuelForThisFrame;
			mMovement += deltaT * mAcceleration;
		} else {
			deactivateJumpJets();
		}
	} else {
		mJumpJetFuel = std::min<double>(mJumpJetFuel + 0.5 * deltaT, jjs);
		if (hasGroundContact()) {
			if (abs(m_speedDelta) > 0.1) {
				setSpeed(getSpeed() + 40.0 * m_speedDelta * deltaT);
			}

			mMovement = getSpeed() * p_front; // mobility is already factored into m_speed
			mMovement.y = 0.0;
			mAcceleration.y = 0.0;
		} else {
			// todo: when in the air, the mech may turn but needs to keep its direction of movement
			mAcceleration.y -= gravity * deltaT;
			mMovement += deltaT * mAcceleration;
		}
	}

	RETURN;
}

// Mark all criticals in one section as destroyed and check for additional effects
void QuadMech::destroyLimb(int section)
{
	// Mark all criticals in that section as destroyed
	SET_SCOPE;
	for (int i = 0; i < getCriticalCount(section); ++i) {
		mCriticalDamaged[section][i] = true;
	}

	SET_OP;
	mArmorDamageFront[section] = mFrontArmor[section];
	mArmorDamageRear[section]  = mRearArmor[section];
	mInternalDamage[section]   = mInternalStructure[section];

	SET_OP;
	switch (section) {
		case Biped_Mech_Section_Head:
		case Biped_Mech_Section_Center_Torso:
			// If the head or center torso is destroyed, the whole mech is destroyed
			{
				setStatus(destroyed);
				MechSim* mechSim = getMechSim();
				mechSim->updateObjectStatus(mID, SubObjectiveType::attack, false);
			}
			break;
		case Biped_Mech_Section_Left_Torso:
			// If a side torso is destroyed, the corresponding arm is also destroyed
			destroyLimb(Quad_Mech_Section_Left_Front_Leg);
			break;
		case Biped_Mech_Section_Right_Torso:
			// If a side torso is destroyed, the corresponding arm is also destroyed
			destroyLimb(Quad_Mech_Section_Right_Front_Leg);
			break;
		case Quad_Mech_Section_Left_Front_Leg:
		case Quad_Mech_Section_Right_Front_Leg:
		case Quad_Mech_Section_Left_Rear_Leg:
		case Quad_Mech_Section_Right_Rear_Leg:
			// If a leg is destroyed, mobility drops to zero
			mMobility = 0.0;
			break;
	}

	RETURN;
}

// Check for for critical damage, e.g. if a weapon is destroyed by a critical hit
void QuadMech::checkCriticalDamage(int section, int criticalToDestroy)
{
	if (section >= Quad_Mech_Sections || criticalToDestroy >= (int) mCriticalDamaged[section].size()) {
		return;
	}

	SET_SCOPE;
	mCriticalDamaged[section][criticalToDestroy] = true;
	bool destroyed = false;
	WeaponType weaponType = getCriticals(section, criticalToDestroy);

	const MWEquipment* equipmentStats = getWeaponStats (weaponType);
	if (equipmentStats != nullptr) {
		COUT << "One critical of type " << equipmentStats->longName << " destroyed in section " << section << " on ";
		getName().printLn();
	}

	switch(weaponType) {
		case CE_Engine:
			// Count engine hits. The third engine hit disables the Mech until it can be repaired.
			++mEngineHits;
			destroyed = (mEngineHits > 2);
			break;
		case CE_Gyro:
			// Count gyro hits. The second hit makes it impossible for the mech to stand.
			/*++mGyroHits;
			if (mGyroHits > 1) {
				mMobility = 0.0;
			}*/
			break;
		case CE_Life_Support:
			// todo: Any hit disables the system completely, resulting in damage to the pilot in case of heat or hostile environments
			//mLifeSupportDestroyed = true;
			break;
		case CE_Cockpit:
			// A hit to the cockpit kills the pilot and disable this mech
			destroyed = true;
			break;
		case CE_Sensors:
			// todo ...
			break;
		case CE_Hip:
		case CE_Upper_Leg_Actuator:
		case CE_Lower_Leg_Actuator:
		case CE_Foot:
			// In contrast to the BT rules, any destroyed actuator reduces mobility by 10 %
			mMobility = std::max(0.0, mMobility-0.1);
			break;
		case CE_Jump_Jet:
			// Count jump jet hits
			//++mJumpJetHits;
			break;
		case IS_Heat_Sink:
		case IS_Double_Heat_Sink:
		case Clan_Double_Heat_Sink:
			// Count damaged heat sink criticals
			// (this is a simplification compared to BT rules, where each critical hit can disable a heat sink completely)
			++mHeatSinkCriticalHits;
			break;
		default:
			// todo: a weapon was destroyed, mark it as unusable
			MountedWeapon* weapon = getMountedWeapon(section, criticalToDestroy);
			if (weapon != nullptr) {
				weapon->destroyed = true;
				// todo: check for ammo explosion. Fireworks!
				// todo: check for exploded Gauss rifles ...
				// todo: show destroyed weapons in grey in the HUD, do not allow AI or players to fire destroyed weapons
			}
			break;
	}

	if (destroyed) {
		setStatus(VehicleStatus::destroyed);
		MechSim* mechSim = getMechSim();
		mechSim->updateObjectStatus(mID, SubObjectiveType::attack, false);
	}

	RETURN;
}

// Function to notify vehicle that it is taking damage
void QuadMech::receiveDamage(RenderableObject* limb, double damage, GameObject* sender, const Point3D& pointOfImpact)
{
	if (sender == this || damage <= 0.0) {
		return; // ignore self-inflicted damage ...
	}

	SET_SCOPE;
	if (mAI != nullptr && sender != nullptr) {
		AbstractVehicleAI* vehicleAI = dynamic_cast<AbstractVehicleAI*>(mAI);
		if (vehicleAI != nullptr) {
			vehicleAI->setTarget(sender);
		}
	}

	SET_OP;
	int limbHit = -1;
	size_t i = 0;
	while (i < getObjectCount()) {
		if (getObject(i) == limb) {
			limbHit = i;
			i = getObjectCount();
		} else {
			++i;
		}
	}

	bool damageFromFront = true;
	int section = limb2section(limbHit);

	if (limbHit == Quad_Mech_Part_Torso) {
		// For the torso, find out if front/rear armor is hit
		Point3D position = getPosition();
		Matrix m = RotationMatrix(M_PI * mTorsoTwist / 180.0, 0, 1, 0);
		Point3D rotatedFront = m.Transform(p_front);
		Plane frontRearPlane(position, rotatedFront);
		auto halfSpaceTestResult = frontRearPlane.halfSpaceTest(pointOfImpact);
		if (halfSpaceTestResult < 0.0) {
			damageFromFront = false;
		}

		// Decide between left/right/center torso. As a rule of thumb, if the impact is more than a meter from the plane, a side torso was hit
		Point3D rotatedLeft = mM.Transform(p_left);
		Plane leftRightPlane(position, rotatedFront);
		halfSpaceTestResult = leftRightPlane.halfSpaceTest(pointOfImpact);
		if (halfSpaceTestResult > 1.0) {
			section = Quad_Mech_Section_Right_Torso;
		} else if (halfSpaceTestResult < -1.0) {
			section = Quad_Mech_Section_Left_Torso;
		} else {
			section = Quad_Mech_Section_Center_Torso;
		}
	}

	SET_OP;
	if (section >= 0 && section < (int) Quad_Mech_Sections) {
		if (damageFromFront) {
			SET_OP;
			auto armor = getArmor(section, Vehicle::Armor_Front);
			if (mArmorDamageFront[section] < armor) {
				if (mArmorDamageFront[section] + damage < armor) {
					// Armor completely absorbs the damage
					mArmorDamageFront[section] += damage;
					RETURN;
				} else {
					// Armor absorbs only part of the damage
					double damageDelta = armor - mArmorDamageFront[section];
					mArmorDamageFront[section] += damageDelta;
					damage -= damageDelta;
				}
			}
		} else {
			SET_OP;
			auto armor = getArmor(section, Vehicle::Armor_Rear);
			if (mArmorDamageRear[section] < armor) {
				if (mArmorDamageRear[section] + damage < armor) {
					// Armor completely absorbs the damage
					mArmorDamageRear[section] += damage;
					RETURN;
				} else {
					// Armor absorbs only part of the damage
					double damageDelta = armor - mArmorDamageRear[section];
					mArmorDamageRear[section] += damageDelta;
					damage -= damageDelta;
				}
			}
		}

		SET_OP;
		mInternalDamage[section] += damage;
		size_t sectionDamagedCriticals = std::count(mCriticalDamaged[section].begin(), mCriticalDamaged[section].end(), true);
		if (mInternalDamage[section] > getInternalStructure(section)) {
			// The limb is destroyed, all of its equipment becomes unusable, but this does not cause an internal ammunition explosion
			destroyLimb(section);
		} else {
			// Roll critical hits for the location
			// We cannot roll critical locations normally because of the delta damage increments ...
			// What we can do instead is keep the number of damaged criticals proportional to the damage caused. 
			// When all internal structure is destroyed, the last criticals are lost also. 
			auto sectionCriticals = getCriticalCount(section);
			double criticalsDamagedPercent = 100.0 * sectionDamagedCriticals / sectionCriticals;
			double internalStructureDamagePercent = 100.0 * mInternalDamage[section] / getInternalStructure(section);
			if (internalStructureDamagePercent > criticalsDamagedPercent && (int) sectionDamagedCriticals < sectionCriticals) {
				int criticalToDestroy;
				do {
					criticalToDestroy = irand(0, sectionCriticals);
				} while (mCriticalDamaged[section][criticalToDestroy] == true);
				checkCriticalDamage(section, criticalToDestroy);
			}
		}
	}

	RETURN;
}

// Query damage to armor and internal structure
double QuadMech::getArmorDamage(int section, int armorType) const
{
	SET_SCOPE;
	if (section >= 0 && section < (int) mArmorDamageFront.size()) {
		if (armorType == Vehicle::Armor_Front) {
			RETURN mArmorDamageFront[section];
		} else {
			RETURN mArmorDamageRear[section];
		}
	} else {
		RETURN 0.0;
	}
}

double QuadMech::getInternalDamage(int section) const
{
	SET_SCOPE;
	if (section >= 0 && section < (int) mInternalDamage.size()) {
		RETURN mInternalDamage[section];
	} else {
		RETURN 0.0;
	}
}

// Check if the vehicle's cockpit has a reticle that moves horizontally
bool QuadMech::reticleMovesHorizontally() const
{
	if (torsoTwistLimit < 5.0) {
		return true;
	} else {
		return false;
	}
}

double QuadMech::getWeight () const
{
	return realWeight;
}

// Check for items that the user may not add in the MechLab
bool QuadMech::userMayAddDirectly (int section, WeaponType weaponNo)
{
	SET_SCOPE;
	if (weaponNo == WeaponType::CE_Jump_Jet)
	{
		RETURN mayMountJJs(section);
	}

	SET_OP;
	auto alreadyPresent = countWeaponCriticals(section, weaponNo);
	if (weaponNo == WeaponType::Unused_Critical_Slot
	||  weaponNo == WeaponType::CE_Engine
	||  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
	|| (weaponNo == WeaponType::CE_Hip && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Upper_Leg_Actuator && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Lower_Leg_Actuator && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Foot && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm))
	{
		RETURN false;
	}	else {
		RETURN true;
	}
}

// The point to attack may differ per vehicle class, the center of the volume isn't necessarily a good spot to attack
Point3D QuadMech::getAttackSpot(bool onGround) const
{
	SET_SCOPE;
	Point3D result = mPosition;
	const RenderableObject* torsoGeometry = getObject(Quad_Mech_Part_Torso);
	if (torsoGeometry != nullptr) {
		auto torsoBB = torsoGeometry->getBoundingBox();
		if (torsoBB.valid()) {
			result = torsoBB.getCenter();
		}
	}
	RETURN result;
}