/*
   * eDamageState values:
    * 0 dsUnaffected    - not affected (100% health)
    * 1 dsUnchanged     - didn't change state
    * 2 dsNowYellow     - changed to yellow (only if was in green, otherwise remains unchanged)
    * 3 dsNowRed        - changed to red
    * 4 dsDeath         - changed to dead, Health = 0
    * 5 dsPostMortem    - dead, just cleaning up
  */

eDamageState TechnoClass::ReceiveDamage(int &Damage, int unknown, WarheadTypeClass *WH, TechnoClass *attacker, bool ignoreDefenses, int arg10, HouseClass *attackerHouse) {

  bool negativeDamage = Damage < 0;
  if( !ignoreDefenses && !negativeDamage ) {
    float typeArmorMult = this->OwningPlayer->GetTypeArmorMultiplier(this->GetType()); // [Country]VehicleArmorMult or such
    float selfArmorMult = this->ArmorMultiplier; // armor crate or such
    Damage /= (typeArmorMult * selfArmorMult);
    if( this->IsVeteran() || this->IsElite() ) {
      TechnoTypeClass *Type = this->GetType();
      if (
        ( this->IsVeteran() && ( Type->VeteranAbilities & ABILITY_STRONGER ) ) ||
        ( this->IsElite() && 
          ( ( Type->VeteranAbilities & ABILITY_STRONGER ) 
            || ( Type->EliteAbilities & ABILITY_STRONGER ) ) )
      ) {
        Damage /= Rules->VeteranArmor;
      }
    }
    Damage = max( Damage, 1 ); // if damage < 1 then damage = 1
  }

  if( attacker ) {
    if ( this->GetType()->TypeImmune ) {
      if( attacker->GetType() == this->GetType() ) {
        if( attacker->OwningPlayer == this->OwningPlayer ) {
          return dsUnaffected;
        }
      }
    }
  }

  if( this->IsIronCurtained() ) {
    if( !ignoreDefenses && !negativeDamage ) {
      int CLDisableFlags = DISABLE_NONE;
      if( this->IronCurtained ) {
        CLDisableFlags = DISABLE_GREEN | DISABLE_BLUE;
      }
      FlashbangWarheadAt(  
        Damage * 2,        
        WH,                
        this->pos.X,       
        this->pos.Y,       
        this->pos.Z,       
        1,                 // override warhead's Bright= setting and flash anyway
        CLDisableFlags);
        
      return dsUnaffected;
    }
  }
  
  if( this->GetType()->DamageReducesReadiness ) {
    float ratio = Damage / this->GetType()->Strength;
    ratio *= this->GetType()->ReadinessReductionMultiplier;
    curAmmo = this->currentAmmo;
    this->currentAmmo = int (curAmmo - this->GetType()->Ammo * ratio);
    this->Update_Reloading();
  }

  if( this->Bunker ) {
    if( !ignoreDefenses ) {
      if( this->What_Am_I() == IS_BUILDING ) {
        if( WH && WH->PenetratesBunker ) {
          Damage = 0;
          return dsUnaffected;
        }
      } else {
        if( WH && !WH->PenetratesBunker ) {
          if( this->GetCell()->GetBuilding() == this->Bunker ) {
            Damage = 0;
            return dsUnaffected;
          }
        }
      }
    }
  }

  if( WH ) {
    if( WH->Radiation && this->ImmuneToRadiation ) {
      Damage = 0;
      return dsUnaffected;
    }

    if( WH->PsychicDamage && this->ImmuneToPsionicWeapons ) {
      Damage = 0;
      return dsUnaffected;
    }

    if( WH->Poison && this->ImmuneToPoison ) {
      Damage = 0;
      return dsUnaffected;
    }

    if( !WH->AffectsAllies && attacker && this->OwningPlayer->IsAlly(attacker->OwningPlayer) ) {
      Damage = 0;
      return dsUnaffected;
    }

    if( WH->Psychedelic ) {
      if( this->OwningPlayer->IsAlly(attackerHouse) ) {
        Damage = 0;
        return dsUnaffected;
      }
      if( this->GetType()->ImmuneToPsionics ) {
        Damage = 0;
        return dsUnaffected;
      }

      if( this->What_Am_I() == IS_BUILDING ) {
        Damage = 0;
        return dsUnaffected;
      }

      Damage = GetVersedDamage(Damage, this->GetType()->Armor, WH);
      this->BerzerkDurationLeft = Damage;
      if( !this->Berzerk ) {
        this->Berzerk = 1;
        if( this ) {
          AITeamClass *Team = this->BelongsToTeam;
          if( Team ) {
            Team->LiberateMember(this, -1, 0);
          }
        }
        this->SetTarget(NULL);
        this->QueueMission(MISSION_HUNT);
        return dsUnaffected;
      }
    }
  }
  
  eDamageState state = ObjectClass *::ReceiveDamage(damage, attackerType, WH, attacker, ignoreDefenses, a10, attackerHouse);
  if( target ) {
    TechnoTypeClass *Type = this->GetType();
    float damagedForCredits = Type->GetCost(attacker->OwningPlayer) * (IntToFloat(Damage) / Strength);
    this->OwningPlayer->DamagedForCredits(FloatToInt(damageInCredits));
  }
  
  if( state == dsDeath ) {
    if( this->What_Am_I() == IS_BUILDING && WH && WH->CausesDelayKill ) {
      BuildingClass *Building;
      if( this ) {
        Building = (BuildingClass *)this;
      } else {
        Building = NULL;
      }
      if( Building->BuildingType->EligibleForDelayKill ) {
        float DKFrames = WH->DelayKillFrames;
        float DKMax = WH->DelayKillAtMax;
        float x1 = (DKMax * DKFrames) - DKFrames;
        DKSpread = FloatToInt(WH->CellSpread) * 256;
        x1 /= DKSpread;
        x1 = x1 * int(arg9) + x1;
        int xx1 = FloatToInt(x1);
        if( Building->C4Planted ) {
          if( Building->C4Timer.StartingFrame != -1 ) {
            if( CurrentFrame - Building->C4Timer.StartingFrame < Building->C4Timer.Duration ) {
              Building->C4Timer.Duration -= (CurrentFrame - Building->C4Timer.StartingFrame);
            } else {
              Building->C4Timer.Duration = 0;
            }
            if( xx1 < Building->C4Timer.Duration ) {
              Building->C4Timer.StartFrame = CurrentFrame;
              Building->C4Timer.Duration = xx1;
            }
            Building->C4Planted = 1;
            this->Alive = 1;
            this->Health = 1;
            return dsPostMortem;
          }
        } else {
          Building->C4Planted = 1;
          Building->C4Timer.StartFrame = CurrentFrame;
          Building->C4Timer.Duration = xx1;
          this->Alive = 1;
          this->Health = 1;
          return dsPostMortem;
        }
      }
    }
  }

  if( state == dsPostMortem ) {
    return state;
  }
  
  if( state != dsUnaffected ) {
    this->RadarFlashTimer.StartFrame = CurrentFrame;
    this->RadarFlashTimer.Duration = Rules->RadarCombatFlashTime;
    if( state != dsDeath ) {
      if( this->GetType()->CanDisguise && !this->GetType()->PermaDisguise ) {
        if( this->IsDisguised() ) {
          this->ClearDisguise();
        }
        this->DisguiseBlinkTimer.StartFrame = CurrentFrame;
        this->DisguiseBlinkTimer.Duration = Damage * 2;
      }
    }
  }

  if( !this->Health ) {
    state = dsDeath;
  }

  bool doUnchangedAnyway = 1;

  switch( state ) {
    case dsNowYellow: {
      doUnchangedAnyway = 0;
    
      if( this->GetType()->VoiceFeedback.Length ) {
        if( Random_Ranged(0, 100) <= 30  ) {
          if( this->OwningPlayer->IsPlayer() ) {
            VocClass::PlayIndex(this->GetType()->VoiceFeedback[Random() % this->GetType()->VoiceFeedback.Length], 0, this->pos);
          }
        }
      }
    
    } // no break - falls through to the next case

    case dsUnchanged: 
    case dsPostMortem: {
      if( doUnchangedAnyway ) {
        if( this->GetType()->DamageSound ) {
          VocClass::PlayIndex(this->GetType()->DamageSound, 0, this->pos);
        }

        if( this->GetType()->DamageSound ) { // oh, it's you again
          VocClass::PlayIndex(this->GetType()->DamageSound, 0, this->pos);
        }
        
        if( this->GetType()->ToProtect || this->neverUsedAlwaysFalse ) {
          if( !this->OwningPlayer->IsHumanControlled() ) {
            if( target ) {
              this->ProtectMeFrom(target);
            }
          }
        }
        if( state == dsDeath ) {
          return state;
        }
      }
    } // no break - falls through to the next case

    case dsUnaffected: 
    case dsNowRed: {
      if( target ) {
        if( !this->OwningPlayer->IsAlliedObject(target) ) {
          this->HasBeenAttacked = 1;
        }
      }
      this->Uncloak();
      if( this->GetHealthPercentage() > Rules->ConditionYellow ) {
        if( this->DamagedParticleSystem ) {
          this->DamagedParticleSystem->UnInit();
        }
      } else if ( state == dsNowYellow || state == dsNowRed ) {
        vector<ParticleSystemTypeClass *> Parts;
        TechnoTypeClass *Type = this->GetType();
        for( int i = 0; i < Type->DamageParticleSystems.Length; ++i) {
          ParticleSystemTypeClass *part = Type->DamageParticleSystems[i];
          if( part->BehavesLike == BEHAVES_SMOKE ) {
            Parts.Append(part);
          }
        }
        if( !this->DamagedParticleSystem && Parts.Length ) {
          if( this->GetHeight() > -10 ) {
            dwXYZ *coords = this->GetType()->GetDamageParticlePosition();
            ParticleSystemClass *PartSys = new ParticleSystemClass(
              Parts[Random_Ranged(0, Parts.Length - 1)], // ParticleSystemTypeClass *psType
              this->Pos,                                 // dwXYZ *Position
              NULL,                                      // ObjectClass *Target
              this,                                      // TechnoClass *Owner, 
              this->Pos,                                 // dwXYZ *particleSpawnPosition,
              0                                          // int unknown
            );
            this->DamagedParticleSystem = PartSys;
          }
        }      
      }
      if( !damageNegative ) {
        if( this->ShouldRetaliate(target) ) {
          if( target ) {
            int idxWeapon = this->SelectWeapon(target);
            if( !this->IsCloseEnough(target, idxWeapon) && this->OwningPlayer->IsHumanControlled() ) {
              dwXYZ *xyzTarget = target->GetCoords();
              dwXYZ *xyzMyself = this->GetCoords();
              int Distance = FloatToInt(float_sqrt(XYZ_Distance(xyzTarget, xyzMyself)));
              float SightRange = (this->GetType()->Sight + 0.5) * 256.0;
              if( Distance < SightRange ) {
                this->SetNewTarget(MISSION_MOVE, target, 0);
              }
            } else {
              this->SetNewTarget(MISSION_MOVE, target, 0);
            }
          }
          if( this ) {
            if( this->derivationFlags & dfFoot ) { // Infantry/Vehicle/Aircraft, no Building
              if( !this->Target && !this->Destination ) {
                if( Rules->PlayerScatter ||
                  ( this->IsVeteran() && ( Type->VeteranAbilities & ABILITY_SCATTER ) ) ||
                    ( this->IsElite() && 
                    ( ( Type->VeteranAbilities & ABILITY_SCATTER ) 
                    || ( Type->EliteAbilities & ABILITY_SCATTER ) ) ) {
                    this->Scatter(this->Pos, 1, 0);
                    return state;
                }
              }
            }
          }
        }
        if( this->derivationFlags & dfFoot ) {
          if( this->GetMissionData()->Scatter ) {
            if( !this->IsBusy ) {
              if( !this->Locomotor->IsMoving() ) {
                if( !this->Target && !this->Destination ) {
                  if( this->What_Am_I() != IS_AIRCRAFT ) {
                    if( this->OwningPlayer->IsHumanControlled() ) {
                      if( Rules->PlayerScatter ||
                        ( this->IsVeteran() && ( Type->VeteranAbilities & ABILITY_SCATTER ) ) ||
                          ( this->IsElite() && 
                          ( ( Type->VeteranAbilities & ABILITY_SCATTER ) 
                          || ( Type->EliteAbilities & ABILITY_SCATTER ) ) ) {
                          this->Scatter(this->Pos, 1, 0);
                          return state;
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
      return state;
    }
    break;
    
    case dsDead: {
      if( this->SlaveManager ) {
        this->SlaveManager->ReleaseSlaves();
      }

      if( this->DrainTarget ) {
        if( this->DrainAnimation ) {
          this->DrainAnimation->UnInit();
          this->DrainAnimation = NULL;
        }
        if( this->DrainTarget ) {
          this->DrainTarget->DrainingMe = NULL;
          if( this->DrainTarget->OwningPlayer ) {
            this->DrainTarget->OwningPlayer->RecheckTechLevels = 1;
          }
          this->DrainTarget = NULL;
        }
      }
      
      if( this->DrainingMe ) {
        if( this->DrainingMe->DrainAnimation ) {
          this->DrainingMe->DrainAnimation->UnInit();
          this->DrainingMe->DrainAnimation = NULL;
        }

        if( this->DrainingMe->DrainTarget ) {
          this->DrainingMe->DrainTarget->DrainingMe = NULL;
          this->DrainingMe->DrainTarget->OwningPlayer->RecheckTechLevels = 1;
          this->DrainingMe->DrainTarget = NULL;
        }
      }

      if( this->CaptureManager ) {
        this->CaptureManager->ReleaseAll();
      }
      
      if( this->GetType()->VoiceDie.Length && this->OwningPlayer->IsPlayer() ) {
        VocClass::PlayIndex(this->GetType()->VoiceDie[Random() % this->GetType()->VoiceDie.Length], 0, this->pos);
      }

      if( this->GetType()->DieSound.Length && this->OwningPlayer->IsPlayer() ) {
        VocClass::PlayIndex(this->GetType()->DieSound[Random() % this->GetType()->DieSound.Length], 0, this->pos);
      }

      if ( this->SpawnManager ) {
        this->SpawnManager->KamikazeNodes();
      }

      this->Deselect();
      
      if( this->FireParticleSystem ) {
        this->FireParticleSystem->UnInit();
        this->FireParticleSystem = NULL;
      }
      
      if( this->GetHeight > 10 || Map->GetCellAt(this->pos)->LandType != IS_WATER ) {
        int totalDebris;
        if( this->GetType->MaxDebris > 0 ) {
          totalDebris = Random_Ranged(this->GetType()->MinDebris, this->GetType()->MaxDebris - 1);
          if( this->GetType()->DebrisTypes.Length && totalDebris) {
            for( int i = 0; i < this->GetType()->DebrisMaximums.Length; ++i ) {
              int amount = Random() % this->GetType()->DebrisMaximums[i];
              amount = max(amount, totalDebris);
              for( int j = 0; j < amount; ++j) {
                new VoxelAnimationClass(this->GetType()->DebrisTypes[i], this->GetCoords());
                --totalDebris;
              }
            }
          }
        }
        
        if( this->GetType()->DebrisAnims.Length ) {
          while( totalDebris ) {
            new AnimClass(
                                 // AnimType 
              this->GetType()->DebrisAnims[Random_Ranged(0, this->GetType()->DebrisAnims.Length - 1)],
              this->GetCoords(), // Coords
              0, 
              1,                 // RepeatTimes
              sizeof(AnimClass),
              0,                 // ForceZAdjust
              0);
            --totalDebris;
          }
        } else if( !this->GetType()->DebrisAnims.Length ) { // department of redundancy department
          while( totalDebris ) {
            new AnimClass(
                                 // AnimType 
              Rules->MetallicDebris[Random_Ranged(0, Rules->MetallicDebris.Length - 1)],
              this->GetCoords(), // Coords
              0, 
              1,                 // RepeatTimes
              sizeof(AnimClass),
              0,                 // ForceZAdjust
              0);
            --totalDebris;
          }
        }
        
        WeaponTypeClass *Weapon = this->GetWeapon(this->CurrentTurretNumber);

        if( this->GetType()->Explodes ||
            ( this->IsVeteran() && ( Type->VeteranAbilities & ABILITY_EXPLODES ) ) ||
            ( this->IsElite() && 
            ( ( Type->VeteranAbilities & ABILITY_EXPLODES ) || ( Type->EliteAbilities & ABILITY_EXPLODES ) ) ) ) {
            
          if( Weapon && Weapon->Suicide ) {
            while( this->Passengers.First ) {
              FootClass *Hiker = this->GetFirstPassenger;
              if( Hiker->BelongsToTeam ) {
                Hiker->BelongsToTeam->LiberateMember(Hiker, -1, 0);
              }
              Hiker = this->RemoveFirstPassenger();
              if( Hiker ) {
                Hiker->AnnounceDestruction();
                Hiker->UnInit();
              }
            }
            this->FireDeathWeapon(0);
          }
        }
      }
      if( this->IvanBomb ) {
        this->IvanBomb->Detonate();
        return state;
      }
      return state;
    }
    break;
  }

}