#include "g_local.h"
bool CanDamage(edict_t *targ, edict_t *inflictor) {
  vec3_t dest;
  trace_t trace;
    if(targ->movetype == MOVETYPE_PUSH) {
    VectorAdd(targ->absmin, targ->absmax, dest);
    VectorScale(dest, 0.5, dest);
    trace =
        gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
    if(trace.fraction == 1.0)
      return true;
    if(trace.ent == targ)
      return true;
    return false;
  }
  trace = gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor,
                   MASK_SOLID);
  if(trace.fraction == 1.0)
    return true;
  VectorCopy(targ->s.origin, dest);
  dest[0] += 15.0;
  dest[1] += 15.0;
  trace =
      gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
  if(trace.fraction == 1.0)
    return true;
  VectorCopy(targ->s.origin, dest);
  dest[0] += 15.0;
  dest[1] -= 15.0;
  trace =
      gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
  if(trace.fraction == 1.0)
    return true;
  VectorCopy(targ->s.origin, dest);
  dest[0] -= 15.0;
  dest[1] += 15.0;
  trace =
      gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
  if(trace.fraction == 1.0)
    return true;
  VectorCopy(targ->s.origin, dest);
  dest[0] -= 15.0;
  dest[1] -= 15.0;
  trace =
      gi.trace(inflictor->s.cmodel_index, inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
  if(trace.fraction == 1.0)
    return true;
  return false;
}
void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) {
  if(ent_read_health(targ)->value < -999) {
    dv_set(ent_write_health(targ), -999);
  }
  targ->enemy = attacker;
  if((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
        if(!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) {
      level.killed_monsters++;
      if(coop->value && attacker->client)
        attacker->client->resp.score++;
            if(strcmp(attacker->classname, "monster_medic") == 0)
        targ->owner = attacker;
    }
  }
  if(targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP ||
     targ->movetype == MOVETYPE_NONE) {     targ->die(targ, inflictor, attacker, damage, point);
    return;
  }
  if((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
    targ->touch = NULL;
    monster_death_use(targ);
  }
  targ->die(targ, inflictor, attacker, damage, point);
}
void SpawnDamage(int type, int cmodel_index, vec3_t origin, vec3_t normal, int damage) {
  if(damage > 255)
    damage = 255;
  gi.WriteByte(svc_temp_entity);
  gi.WriteByte(type);
    gi.WritePosition(origin);
  gi.WriteDir(normal);
  gi.multicast(cmodel_index, origin, MULTICAST_PVS);
}
static int CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) {
  gclient_t *client;
  int save;
  int power_armor_type;
  int index;
  int damagePerCell;
  int pa_te_type;
  int power;
  int power_used;
  if(!damage)
    return 0;
  client = ent->client;
  if(dflags & DAMAGE_NO_ARMOR)
    return 0;
  if(client) {
    power_armor_type = PowerArmorType(ent);
    if(power_armor_type != POWER_ARMOR_NONE) {
      index = ITEM_INDEX(FindItem("Cells"));
      power = client->pers.inventory[index];
    }
  } else if(ent->svflags & SVF_MONSTER) {
    power_armor_type = ent->monsterinfo.power_armor_type;
    power = ent->monsterinfo.power_armor_power;
  } else
    return 0;
  if(power_armor_type == POWER_ARMOR_NONE)
    return 0;
  if(!power)
    return 0;
  if(power_armor_type == POWER_ARMOR_SCREEN) {
    vec3_t vec;
    float dot;
    vec3_t forward;
        AngleVectors(ent->s.angles, forward, NULL, NULL);
    VectorSubtract(point, ent->s.origin, vec);
    VectorNormalize(vec);
    dot = DotProduct(vec, forward);
    if(dot <= 0.3)
      return 0;
    damagePerCell = 1;
    pa_te_type = TE_SCREEN_SPARKS;
    damage = damage / 3;
  } else {
    damagePerCell = 2;
    pa_te_type = TE_SHIELD_SPARKS;
    damage = (2 * damage) / 3;
  }
  save = power * damagePerCell;
  if(!save)
    return 0;
  if(save > damage)
    save = damage;
  SpawnDamage(pa_te_type, ent->s.cmodel_index, point, normal, save);
  ent->powerarmor_time = level.time + 0.2;
  power_used = save / damagePerCell;
  if(client)
    client->pers.inventory[index] -= power_used;
  else
    ent->monsterinfo.power_armor_power -= power_used;
  return save;
}
static int CheckArmor(edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) {
  gclient_t *client;
  int save;
  int index;
  gitem_t *armor;
  if(!damage)
    return 0;
  client = ent->client;
  if(!client)
    return 0;
  if(dflags & DAMAGE_NO_ARMOR)
    return 0;
  index = ArmorIndex(ent);
  if(!index)
    return 0;
  armor = GetItemByIndex(index);
  if(dflags & DAMAGE_ENERGY)
    save = ceil(((gitem_armor_t *)armor->info)->energy_protection * damage);
  else
    save = ceil(((gitem_armor_t *)armor->info)->normal_protection * damage);
  if(save >= client->pers.inventory[index])
    save = client->pers.inventory[index];
  if(!save)
    return 0;
  client->pers.inventory[index] -= save;
  SpawnDamage(te_sparks, ent->s.cmodel_index, point, normal, save);
  return save;
}
void M_ReactToDamage(edict_t *targ, edict_t *attacker) {
  if(!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
    return;
  if(attacker == targ || attacker == targ->enemy)
    return;
      if(targ->monsterinfo.aiflags & AI_GOOD_GUY) {
    if(attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
      return;
  }
  
    if(attacker->client) {
    targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
            if(targ->enemy && targ->enemy->client) {
      if(visible(targ, targ->enemy)) {
        targ->oldenemy = attacker;
        return;
      }
      targ->oldenemy = targ->enemy;
    }
    targ->enemy = attacker;
    if(!(targ->monsterinfo.aiflags & AI_DUCKED))
      FoundTarget(targ);
    return;
  }
      if(((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
     (strcmp(targ->classname, attacker->classname) != 0) && (strcmp(attacker->classname, "monster_tank") != 0) &&
     (strcmp(attacker->classname, "monster_supertank") != 0) && (strcmp(attacker->classname, "monster_makron") != 0) &&
     (strcmp(attacker->classname, "monster_jorg") != 0)) {
    if(targ->enemy && targ->enemy->client)
      targ->oldenemy = targ->enemy;
    targ->enemy = attacker;
    if(!(targ->monsterinfo.aiflags & AI_DUCKED))
      FoundTarget(targ);
  }
    else if(attacker->enemy == targ) {
    if(targ->enemy && targ->enemy->client)
      targ->oldenemy = targ->enemy;
    targ->enemy = attacker;
    if(!(targ->monsterinfo.aiflags & AI_DUCKED))
      FoundTarget(targ);
  }
    else if(attacker->enemy && attacker->enemy != targ) {
    if(targ->enemy && targ->enemy->client)
      targ->oldenemy = targ->enemy;
    targ->enemy = attacker->enemy;
    if(!(targ->monsterinfo.aiflags & AI_DUCKED))
      FoundTarget(targ);
  }
}
bool CheckTeamDamage(edict_t *targ, edict_t *attacker) {
      return false;
}
void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage,
              int knockback, int dflags, int mod) {
  gclient_t *client;
  int take;
  int save;
  int asave;
  int psave;
  int te_sparks;
  if(!targ->takedamage)
    return;
        if((targ != attacker) &&
     ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) {
    if(OnSameTeam(targ, attacker)) {
      if((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
        damage = 0;
      else
        mod |= MOD_FRIENDLY_FIRE;
    }
  }
  meansOfDeath = mod;
    if(skill->value == 0 && deathmatch->value == 0 && targ->client) {
    damage *= 0.5;
    if(!damage)
      damage = 1;
  }
  client = targ->client;
  if(dflags & DAMAGE_BULLET)
    te_sparks = TE_BULLET_SPARKS;
  else
    te_sparks = TE_SPARKS;
  VectorNormalize(dir);
    if(!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) &&
     (ent_read_health(targ)->value > 0))
    damage *= 2;
  if(targ->flags & FL_NO_KNOCKBACK)
    knockback = 0;
    if(!(dflags & DAMAGE_NO_KNOCKBACK)) {
    if((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) &&
       (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) {
      vec3_t kvel;
      float mass;
      if(targ->mass < 50)
        mass = 50;
      else
        mass = targ->mass;
      if(targ->client && attacker == targ)
        VectorScale(dir, 1600.0 * (float)knockback / mass, kvel);       else
        VectorScale(dir, 500.0 * (float)knockback / mass, kvel);
      VectorAdd(targ->velocity, kvel, targ->velocity);
    }
  }
  take = damage;
  save = 0;
    if((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION)) {
    take = 0;
    save = damage;
    SpawnDamage(te_sparks, targ->s.cmodel_index, point, normal, save);
  }
    if((client && client->invincible_framenum > level.framenum) && !(dflags & DAMAGE_NO_PROTECTION)) {
    if(targ->pain_debounce_time < level.time) {
      gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
      targ->pain_debounce_time = level.time + 2;
    }
    take = 0;
    save = damage;
  }
  psave = CheckPowerArmor(targ, point, normal, take, dflags);
  take -= psave;
  asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
  take -= asave;
    asave += save;
    if(!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage(targ, attacker))
    return;
    if(take) {
    if((targ->svflags & SVF_MONSTER) || (client))
      SpawnDamage(TE_BLOOD, targ->s.cmodel_index, point, normal, take);
    else
      SpawnDamage(te_sparks, targ->s.cmodel_index, point, normal, take);
    dv_adjust(ent_write_health(targ), level.time, -take, cv_get(ent_read_max_health(targ)));
    if(ent_read_health(targ)->value <= 0) {
      if((targ->svflags & SVF_MONSTER) || (client))
        targ->flags |= FL_NO_KNOCKBACK;
      Killed(targ, inflictor, attacker, take, point);
      return;
    }
  }
  if(targ->svflags & SVF_MONSTER) {
    M_ReactToDamage(targ, attacker);
    if(!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) {
      targ->pain(targ, attacker, knockback, take);
            if(skill->value == 3)
        targ->pain_debounce_time = level.time + 5;
    }
  } else if(client) {
    if(!(targ->flags & FL_GODMODE) && (take))
      targ->pain(targ, attacker, knockback, take);
  } else if(take) {
    if(targ->pain)
      targ->pain(targ, attacker, knockback, take);
  }
        if(client) {
    client->damage_parmor += psave;
    client->damage_armor += asave;
    client->damage_blood += take;
    client->damage_knockback += knockback;
    VectorCopy(point, client->damage_from);
  }
}
void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) {
  float points;
  edict_t *ent = NULL;
  vec3_t v;
  vec3_t dir;
  while((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) {
    if(ent == ignore)
      continue;
    if(!ent->takedamage)
      continue;
    VectorAdd(ent->mins, ent->maxs, v);
    VectorMA(ent->s.origin, 0.5, v, v);
    VectorSubtract(inflictor->s.origin, v, v);
    points = damage - 0.5 * VectorLength(v);
    if(ent == attacker)
      points = points * 0.5;
    if(points > 0) {
      if(CanDamage(ent, inflictor)) {
        VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
        T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points,
                 DAMAGE_RADIUS, mod);
      }
    }
  }
}