/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "g_local.h"

bool Pickup_Weapon(edict_t *ent, edict_t *other);
void Use_Weapon(edict_t *ent, gitem_t *inv);
void Drop_Weapon(edict_t *ent, gitem_t *inv);

void Weapon_Blaster(edict_t *ent);
void Weapon_Shotgun(edict_t *ent);
void Weapon_SuperShotgun(edict_t *ent);
void Weapon_Machinegun(edict_t *ent);
void Weapon_Chaingun(edict_t *ent);
void Weapon_HyperBlaster(edict_t *ent);
void Weapon_RocketLauncher(edict_t *ent);
void Weapon_Grenade(edict_t *ent);
void Weapon_GrenadeLauncher(edict_t *ent);
void Weapon_Railgun(edict_t *ent);
void Weapon_BFG(edict_t *ent);

gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};

int jacket_armor_index;
int combat_armor_index;
int body_armor_index;
static int power_screen_index;
static int power_shield_index;

#define HEALTH_IGNORE_MAX 1
#define HEALTH_TIMED 2

void Use_Quad(edict_t *ent, gitem_t *item);
static int quad_drop_timeout_hack;

//======================================================================

/*
===============
GetItemByIndex
===============
*/
gitem_t *GetItemByIndex(int index) {
  if(index == 0 || index >= game.num_items)
    return NULL;

  return &itemlist[index];
}

/*
===============
FindItemByClassname

===============
*/
gitem_t *FindItemByClassname(char *classname) {
  int i;
  gitem_t *it;

  it = itemlist;
  for(i = 0; i < game.num_items; i++, it++) {
    if(!it->classname)
      continue;
    if(!Q_stricmp(it->classname, classname))
      return it;
  }

  return NULL;
}

/*
===============
FindItem

===============
*/
gitem_t *FindItem(char *pickup_name) {
  int i;
  gitem_t *it;

  it = itemlist;
  for(i = 0; i < game.num_items; i++, it++) {
    if(!it->pickup_name)
      continue;
    if(!Q_stricmp(it->pickup_name, pickup_name))
      return it;
  }

  return NULL;
}

//======================================================================

void DoRespawn(edict_t *ent) {
  if(ent->team) {
    edict_t *master;
    int count;
    int choice;

    master = ent->teammaster;

    for(count = 0, ent = master; ent; ent = ent->chain, count++)
      ;

    choice = rand() % count;

    for(count = 0, ent = master; count < choice; ent = ent->chain, count++)
      ;
  }

  ent->svflags &= ~SVF_NOCLIENT;
  ent->solid = SOLID_TRIGGER;
  gi.linkentity(ent);

  // send an effect
  ent->s.event = EV_ITEM_RESPAWN;
}

void SetRespawn(edict_t *ent, float delay) {
  ent->flags |= FL_RESPAWN;
  ent->svflags |= SVF_NOCLIENT;
  ent->solid = SOLID_NOT;
  ent->nextthink = level.time + delay;
  ent->think = DoRespawn;
  gi.linkentity(ent);
}

//======================================================================

bool Pickup_Powerup(edict_t *ent, edict_t *other) {
  int quantity;

  quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
  if((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
    return false;

  if((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
    return false;

  other->client->pers.inventory[ITEM_INDEX(ent->item)]++;

  if(deathmatch->value) {
    if(!(ent->spawnflags & DROPPED_ITEM))
      SetRespawn(ent, ent->item->quantity);
    if(((int)dmflags->value & DF_INSTANT_ITEMS) ||
       ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) {
      if((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))
        quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME;
      ent->item->use(other, ent->item);
    }
  }

  return true;
}

void Drop_General(edict_t *ent, gitem_t *item) {
  Drop_Item(ent, item);
  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);
}

//======================================================================

bool Pickup_Adrenaline(edict_t *ent, edict_t *other) {
  if(!deathmatch->value) {
    cv_add(ent_write_max_health(other), 1);
  }

  dv_set(ent_write_health(other), cv_get(ent_read_max_health(other)));

  if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(ent, ent->item->quantity);

  return true;
}

bool Pickup_AncientHead(edict_t *ent, edict_t *other) {
  cv_add(ent_write_max_health(other), 2);

  if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(ent, ent->item->quantity);

  return true;
}

bool Pickup_Bandolier(edict_t *ent, edict_t *other) {
  gitem_t *item;
  int index;

  if(other->client->pers.max_bullets < 250)
    other->client->pers.max_bullets = 250;
  if(other->client->pers.max_shells < 150)
    other->client->pers.max_shells = 150;
  if(other->client->pers.max_cells < 250)
    other->client->pers.max_cells = 250;
  if(other->client->pers.max_slugs < 75)
    other->client->pers.max_slugs = 75;

  item = FindItem("Bullets");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_bullets)
      other->client->pers.inventory[index] = other->client->pers.max_bullets;
  }

  item = FindItem("Shells");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_shells)
      other->client->pers.inventory[index] = other->client->pers.max_shells;
  }

  if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(ent, ent->item->quantity);

  return true;
}

bool Pickup_Pack(edict_t *ent, edict_t *other) {
  gitem_t *item;
  int index;

  if(other->client->pers.max_bullets < 300)
    other->client->pers.max_bullets = 300;
  if(other->client->pers.max_shells < 200)
    other->client->pers.max_shells = 200;
  if(other->client->pers.max_rockets < 100)
    other->client->pers.max_rockets = 100;
  if(other->client->pers.max_grenades < 100)
    other->client->pers.max_grenades = 100;
  if(other->client->pers.max_cells < 300)
    other->client->pers.max_cells = 300;
  if(other->client->pers.max_slugs < 100)
    other->client->pers.max_slugs = 100;

  item = FindItem("Bullets");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_bullets)
      other->client->pers.inventory[index] = other->client->pers.max_bullets;
  }

  item = FindItem("Shells");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_shells)
      other->client->pers.inventory[index] = other->client->pers.max_shells;
  }

  item = FindItem("Cells");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_cells)
      other->client->pers.inventory[index] = other->client->pers.max_cells;
  }

  item = FindItem("Grenades");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_grenades)
      other->client->pers.inventory[index] = other->client->pers.max_grenades;
  }

  item = FindItem("Rockets");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_rockets)
      other->client->pers.inventory[index] = other->client->pers.max_rockets;
  }

  item = FindItem("Slugs");
  if(item) {
    index = ITEM_INDEX(item);
    other->client->pers.inventory[index] += item->quantity;
    if(other->client->pers.inventory[index] > other->client->pers.max_slugs)
      other->client->pers.inventory[index] = other->client->pers.max_slugs;
  }

  if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(ent, ent->item->quantity);

  return true;
}

//======================================================================

void Use_Quad(edict_t *ent, gitem_t *item) {
  int timeout;

  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);

  if(quad_drop_timeout_hack) {
    timeout = quad_drop_timeout_hack;
    quad_drop_timeout_hack = 0;
  } else {
    timeout = 300;
  }

  if(ent->client->quad_framenum > level.framenum)
    ent->client->quad_framenum += timeout;
  else
    ent->client->quad_framenum = level.framenum + timeout;

  gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
}

//======================================================================

void Use_Breather(edict_t *ent, gitem_t *item) {
  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);

  if(ent->client->breather_framenum > level.framenum)
    ent->client->breather_framenum += 300;
  else
    ent->client->breather_framenum = level.framenum + 300;

  //	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
}

//======================================================================

void Use_Envirosuit(edict_t *ent, gitem_t *item) {
  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);

  if(ent->client->enviro_framenum > level.framenum)
    ent->client->enviro_framenum += 300;
  else
    ent->client->enviro_framenum = level.framenum + 300;

  //	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
}

//======================================================================

void Use_Invulnerability(edict_t *ent, gitem_t *item) {
  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);

  if(ent->client->invincible_framenum > level.framenum)
    ent->client->invincible_framenum += 300;
  else
    ent->client->invincible_framenum = level.framenum + 300;

  gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
}

//======================================================================

void Use_Silencer(edict_t *ent, gitem_t *item) {
  ent->client->pers.inventory[ITEM_INDEX(item)]--;
  ValidateSelectedItem(ent);
  ent->client->silencer_shots += 30;

  //	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
}

//======================================================================

bool Pickup_Key(edict_t *ent, edict_t *other) {
  if(coop->value) {
    if(strcmp(ent->classname, "key_power_cube") == 0) {
      if(other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00) >> 8))
        return false;
      other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
      other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8);
    } else {
      if(other->client->pers.inventory[ITEM_INDEX(ent->item)])
        return false;
      other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
    }
    return true;
  }
  other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
  return true;
}

//======================================================================

bool Add_Ammo(edict_t *ent, gitem_t *item, int count) {
  int index;
  int max;

  if(!ent->client)
    return false;

  if(item->tag == AMMO_BULLETS)
    max = ent->client->pers.max_bullets;
  else if(item->tag == AMMO_SHELLS)
    max = ent->client->pers.max_shells;
  else if(item->tag == AMMO_ROCKETS)
    max = ent->client->pers.max_rockets;
  else if(item->tag == AMMO_GRENADES)
    max = ent->client->pers.max_grenades;
  else if(item->tag == AMMO_CELLS)
    max = ent->client->pers.max_cells;
  else if(item->tag == AMMO_SLUGS)
    max = ent->client->pers.max_slugs;
  else
    return false;

  index = ITEM_INDEX(item);

  if(ent->client->pers.inventory[index] == max)
    return false;

  ent->client->pers.inventory[index] += count;

  if(ent->client->pers.inventory[index] > max)
    ent->client->pers.inventory[index] = max;

  return true;
}

bool Pickup_Ammo(edict_t *ent, edict_t *other) {
  int oldcount;
  int count;
  bool weapon;

  weapon = (ent->item->flags & IT_WEAPON);
  if((weapon) && ((int)dmflags->value & DF_INFINITE_AMMO))
    count = 1000;
  else if(ent->count)
    count = ent->count;
  else
    count = ent->item->quantity;

  oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];

  if(!Add_Ammo(other, ent->item, count))
    return false;

  if(weapon && !oldcount) {
    if(other->client->pers.weapon != ent->item &&
       (!deathmatch->value || other->client->pers.weapon == FindItem("blaster")))
      other->client->newweapon = ent->item;
  }

  if(!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
    SetRespawn(ent, 30);
  return true;
}

void Drop_Ammo(edict_t *ent, gitem_t *item) {
  edict_t *dropped;
  int index;

  index = ITEM_INDEX(item);
  dropped = Drop_Item(ent, item);
  if(ent->client->pers.inventory[index] >= item->quantity)
    dropped->count = item->quantity;
  else
    dropped->count = ent->client->pers.inventory[index];

  if(ent->client->pers.weapon && ent->client->pers.weapon->tag == AMMO_GRENADES && item->tag == AMMO_GRENADES &&
     ent->client->pers.inventory[index] - dropped->count <= 0) {
    gi.client_printf(ent, PRINT_HIGH, "Can't drop current weapon\n");
    G_FreeEdict(dropped);
    return;
  }

  ent->client->pers.inventory[index] -= dropped->count;
  ValidateSelectedItem(ent);
}

//======================================================================

void MegaHealth_think(edict_t *self) {
  if(ent_read_health(self->owner)->value > cv_get(ent_read_max_health(self->owner))) {
    self->nextthink = level.time + 1;
    dv_adjust(ent_write_health(self->owner), level.time, -1, cv_get(ent_read_max_health(self->owner)));
    return;
  }

  if(!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(self, 20);
  else
    G_FreeEdict(self);
}

bool Pickup_Health(edict_t *ent, edict_t *other) {
  if(!(ent->style & HEALTH_IGNORE_MAX))
    if(ent_read_health(other)->value >= cv_get(ent_read_max_health(other)))
      return false;

  dv_adjust(ent_write_health(other), level.time, ent->count, cv_get(ent_read_max_health(other)));

  if(!(ent->style & HEALTH_IGNORE_MAX)) {
    if(ent_read_health(other)->value > cv_get(ent_read_max_health(other)))
      dv_set(ent_write_health(other), cv_get(ent_read_max_health(other)));
  }

  if(ent->style & HEALTH_TIMED) {
    ent->think = MegaHealth_think;
    ent->nextthink = level.time + 5;
    ent->owner = other;
    ent->flags |= FL_RESPAWN;
    ent->svflags |= SVF_NOCLIENT;
    ent->solid = SOLID_NOT;
  } else {
    if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
      SetRespawn(ent, 30);
  }

  return true;
}

//======================================================================

int ArmorIndex(edict_t *ent) {
  if(!ent->client)
    return 0;

  if(ent->client->pers.inventory[jacket_armor_index] > 0)
    return jacket_armor_index;

  if(ent->client->pers.inventory[combat_armor_index] > 0)
    return combat_armor_index;

  if(ent->client->pers.inventory[body_armor_index] > 0)
    return body_armor_index;

  return 0;
}

bool Pickup_Armor(edict_t *ent, edict_t *other) {
  int old_armor_index;
  gitem_armor_t *oldinfo;
  gitem_armor_t *newinfo;
  int newcount;
  float salvage;
  int salvagecount;

  // get info on new armor
  newinfo = (gitem_armor_t *)ent->item->info;

  old_armor_index = ArmorIndex(other);

  // handle armor shards specially
  if(ent->item->tag == ARMOR_SHARD) {
    if(!old_armor_index)
      other->client->pers.inventory[jacket_armor_index] = 2;
    else
      other->client->pers.inventory[old_armor_index] += 2;
  }

  // if player has no armor, just use it
  else if(!old_armor_index) {
    other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count;
  }

  // use the better armor
  else {
    // get info on old armor
    if(old_armor_index == jacket_armor_index)
      oldinfo = &jacketarmor_info;
    else if(old_armor_index == combat_armor_index)
      oldinfo = &combatarmor_info;
    else // (old_armor_index == body_armor_index)
      oldinfo = &bodyarmor_info;

    if(newinfo->normal_protection > oldinfo->normal_protection) {
      // calc new armor values
      salvage = oldinfo->normal_protection / newinfo->normal_protection;
      salvagecount = salvage * other->client->pers.inventory[old_armor_index];
      newcount = newinfo->base_count + salvagecount;
      if(newcount > newinfo->max_count)
        newcount = newinfo->max_count;

      // zero count of old armor so it goes away
      other->client->pers.inventory[old_armor_index] = 0;

      // change armor to new item with computed value
      other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
    } else {
      // calc new armor values
      salvage = newinfo->normal_protection / oldinfo->normal_protection;
      salvagecount = salvage * newinfo->base_count;
      newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
      if(newcount > oldinfo->max_count)
        newcount = oldinfo->max_count;

      // if we're already maxed out then we don't need the new armor
      if(other->client->pers.inventory[old_armor_index] >= newcount)
        return false;

      // update current armor value
      other->client->pers.inventory[old_armor_index] = newcount;
    }
  }

  if(!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
    SetRespawn(ent, 20);

  return true;
}

//======================================================================

int PowerArmorType(edict_t *ent) {
  if(!ent->client)
    return POWER_ARMOR_NONE;

  if(!(ent->flags & FL_POWER_ARMOR))
    return POWER_ARMOR_NONE;

  if(ent->client->pers.inventory[power_shield_index] > 0)
    return POWER_ARMOR_SHIELD;

  if(ent->client->pers.inventory[power_screen_index] > 0)
    return POWER_ARMOR_SCREEN;

  return POWER_ARMOR_NONE;
}

void Use_PowerArmor(edict_t *ent, gitem_t *item) {
  int index;

  if(ent->flags & FL_POWER_ARMOR) {
    ent->flags &= ~FL_POWER_ARMOR;
    gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
  } else {
    index = ITEM_INDEX(FindItem("cells"));
    if(!ent->client->pers.inventory[index]) {
      gi.client_printf(ent, PRINT_HIGH, "No cells for power armor.\n");
      return;
    }
    ent->flags |= FL_POWER_ARMOR;
    gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
  }
}

bool Pickup_PowerArmor(edict_t *ent, edict_t *other) {
  int quantity;

  quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];

  other->client->pers.inventory[ITEM_INDEX(ent->item)]++;

  if(deathmatch->value) {
    if(!(ent->spawnflags & DROPPED_ITEM))
      SetRespawn(ent, ent->item->quantity);
    // auto-use for DM only if we didn't already have one
    if(!quantity)
      ent->item->use(other, ent->item);
  }

  return true;
}

void Drop_PowerArmor(edict_t *ent, gitem_t *item) {
  if((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
    Use_PowerArmor(ent, item);
  Drop_General(ent, item);
}

//======================================================================

/*
===============
Touch_Item
===============
*/
void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) {
  bool taken;

  if(!other->client)
    return;
  if(ent_read_health(other)->value < 1)
    return; // dead people can't pickup
  if(!ent->item->pickup)
    return; // not a grabbable item?

  taken = ent->item->pickup(ent, other);

  if(taken) {
    // flash the screen
    other->client->bonus_alpha = 0.25;

    // show icon and name on status bar
    other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
    other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ITEM_INDEX(ent->item);
    other->client->pickup_msg_time = level.time + 3.0;

    // change selected item
    if(ent->item->use)
      other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);

    if(ent->item->pickup == Pickup_Health) {
      if(ent->count == 2)
        gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0);
      else if(ent->count == 10)
        gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0);
      else if(ent->count == 25)
        gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0);
      else // (ent->count == 100)
        gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0);
    } else if(ent->item->pickup_sound) {
      gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
    }
  }

  if(!(ent->spawnflags & ITEM_TARGETS_USED)) {
    G_UseTargets(ent, other);
    ent->spawnflags |= ITEM_TARGETS_USED;
  }

  if(!taken)
    return;

  if(!((coop->value) && (ent->item->flags & IT_STAY_COOP)) ||
     (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) {
    if(ent->flags & FL_RESPAWN)
      ent->flags &= ~FL_RESPAWN;
    else
      G_FreeEdict(ent);
  }
}

//======================================================================

static void drop_temp_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) {
  if(other == ent->owner)
    return;

  Touch_Item(ent, other, plane, surf);
}

static void drop_make_touchable(edict_t *ent) {
  ent->touch = Touch_Item;
  if(deathmatch->value) {
    ent->nextthink = level.time + 29;
    ent->think = G_FreeEdict;
  }
}

edict_t *Drop_Item(edict_t *ent, gitem_t *item) {
  edict_t *dropped;
  vec3_t forward, right;
  vec3_t offset;

  dropped = G_Spawn(ent->s.cmodel_index);

  dropped->classname = item->classname;
  dropped->item = item;
  dropped->spawnflags = DROPPED_ITEM;
  dropped->s.effects = item->world_model_flags;
  dropped->s.renderfx = RF_GLOW;
  VectorSet(dropped->mins, -15, -15, -15);
  VectorSet(dropped->maxs, 15, 15, 15);
  gi.setmodel(dropped, dropped->item->world_model);
  dropped->solid = SOLID_TRIGGER;
  dropped->movetype = MOVETYPE_TOSS;
  dropped->touch = drop_temp_touch;
  dropped->owner = ent;

  if(ent->client) {
    trace_t trace;

    AngleVectors(ent->client->v_angle, forward, right, NULL);
    VectorSet(offset, 24, 0, -16);
    G_ProjectSource(ent->s.origin, offset, forward, right, dropped->s.origin);
    trace = gi.trace(ent->s.cmodel_index, ent->s.origin, dropped->mins, dropped->maxs, dropped->s.origin, ent,
                     CONTENTS_SOLID);
    VectorCopy(trace.endpos, dropped->s.origin);
  } else {
    AngleVectors(ent->s.angles, forward, right, NULL);
    VectorCopy(ent->s.origin, dropped->s.origin);
  }

  VectorScale(forward, 100, dropped->velocity);
  dropped->velocity[2] = 300;

  dropped->think = drop_make_touchable;
  dropped->nextthink = level.time + 1;

  gi.linkentity(dropped);

  return dropped;
}

void Use_Item(edict_t *ent, edict_t *other, edict_t *activator) {
  ent->svflags &= ~SVF_NOCLIENT;
  ent->use = NULL;

  if(ent->spawnflags & ITEM_NO_TOUCH) {
    ent->solid = SOLID_BBOX;
    ent->touch = NULL;
  } else {
    ent->solid = SOLID_TRIGGER;
    ent->touch = Touch_Item;
  }

  gi.linkentity(ent);
}

//======================================================================

/*
================
droptofloor
================
*/
void droptofloor(edict_t *ent) {
  trace_t tr;
  vec3_t dest;
  float *v;

  v = tv(-15, -15, -15);
  VectorCopy(v, ent->mins);
  v = tv(15, 15, 15);
  VectorCopy(v, ent->maxs);

  if(ent->model)
    gi.setmodel(ent, ent->model);
  else
    gi.setmodel(ent, ent->item->world_model);
  ent->solid = SOLID_TRIGGER;
  ent->movetype = MOVETYPE_TOSS;
  ent->touch = Touch_Item;

  v = tv(0, 0, -128);
  VectorAdd(ent->s.origin, v, dest);

  tr = gi.trace(ent->s.cmodel_index, ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
  if(tr.startsolid) {
    gi.dprintf("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
    G_FreeEdict(ent);
    return;
  }

  VectorCopy(tr.endpos, ent->s.origin);

  if(ent->team) {
    ent->flags &= ~FL_TEAMSLAVE;
    ent->chain = ent->teamchain;
    ent->teamchain = NULL;

    ent->svflags |= SVF_NOCLIENT;
    ent->solid = SOLID_NOT;
    if(ent == ent->teammaster) {
      ent->nextthink = level.time + FRAMETIME;
      ent->think = DoRespawn;
    }
  }

  if(ent->spawnflags & ITEM_NO_TOUCH) {
    ent->solid = SOLID_BBOX;
    ent->touch = NULL;
    ent->s.effects &= ~EF_ROTATE;
    ent->s.renderfx &= ~RF_GLOW;
  }

  if(ent->spawnflags & ITEM_TRIGGER_SPAWN) {
    ent->svflags |= SVF_NOCLIENT;
    ent->solid = SOLID_NOT;
    ent->use = Use_Item;
  }

  gi.linkentity(ent);
}

/*
===============
PrecacheItem

Precaches all data needed for a given item.
This will be called for each item spawned in a level,
and for each item in each client's inventory.
===============
*/
void PrecacheItem(gitem_t *it) {
  char *s, *start;
  char data[MAX_QPATH];
  int len;
  gitem_t *ammo;

  if(!it)
    return;

  if(it->pickup_sound)
    gi.soundindex(it->pickup_sound);
  if(it->world_model)
    gi.modelindex(it->world_model);
  if(it->view_model)
    gi.modelindex(it->view_model);
  if(it->icon)
    gi.imageindex(it->icon);

  // parse everything for its ammo
  if(it->ammo && it->ammo[0]) {
    ammo = FindItem(it->ammo);
    if(ammo != it)
      PrecacheItem(ammo);
  }

  // parse the space seperated precache string for other items
  s = it->precaches;
  if(!s || !s[0])
    return;

  while(*s) {
    start = s;
    while(*s && *s != ' ')
      s++;

    len = s - start;
    if(len >= MAX_QPATH || len < 5)
      gi.error("PrecacheItem: %s has bad precache string", it->classname);
    memcpy(data, start, len);
    data[len] = 0;
    if(*s)
      s++;

    // determine type based on extension
    if(!strcmp(data + len - 3, "md2"))
      gi.modelindex(data);
    else if(!strcmp(data + len - 3, "sp2"))
      gi.modelindex(data);
    else if(!strcmp(data + len - 3, "wav"))
      gi.soundindex(data);
    if(!strcmp(data + len - 3, "pcx"))
      gi.imageindex(data);
  }
}

/*
============
SpawnItem

Sets the clipping size and plants the object on the floor.

Items can't be immediately dropped to floor, because they might
be on an entity that hasn't spawned yet.
============
*/
void SpawnItem(edict_t *ent, gitem_t *item) {
  PrecacheItem(item);

  if(ent->spawnflags) {
    if(strcmp(ent->classname, "key_power_cube") != 0) {
      ent->spawnflags = 0;
      gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin));
    }
  }

  // some items will be prevented in deathmatch
  if(deathmatch->value) {
    if((int)dmflags->value & DF_NO_ARMOR) {
      if(item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) {
        G_FreeEdict(ent);
        return;
      }
    }
    if((int)dmflags->value & DF_NO_ITEMS) {
      if(item->pickup == Pickup_Powerup) {
        G_FreeEdict(ent);
        return;
      }
    }
    if((int)dmflags->value & DF_NO_HEALTH) {
      if(item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) {
        G_FreeEdict(ent);
        return;
      }
    }
    if((int)dmflags->value & DF_INFINITE_AMMO) {
      if((item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0)) {
        G_FreeEdict(ent);
        return;
      }
    }
  }

  if(coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) {
    ent->spawnflags |= (1 << (8 + level.power_cubes));
    level.power_cubes++;
  }

  // don't let them drop items that stay in a coop game
  if((coop->value) && (item->flags & IT_STAY_COOP)) {
    item->drop = NULL;
  }

  ent->item = item;
  ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids
  ent->think = droptofloor;
  ent->s.effects = item->world_model_flags;
  ent->s.renderfx = RF_GLOW;
  if(ent->model)
    gi.modelindex(ent->model);
}

//======================================================================

gitem_t itemlist[] = {
    {NULL}, // leave index 0 alone

    //
    // ARMOR
    //

    /*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_armor_body", Pickup_Armor, NULL, NULL, NULL, "misc/ar1_pkup.wav", "models/items/armor/body/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "i_bodyarmor",
     /* pickup */ "Body Armor",
     /* width */ 3, 0, NULL, IT_ARMOR, 0, &bodyarmor_info, ARMOR_BODY,
     /* precache */ ""},

    /*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_armor_combat", Pickup_Armor, NULL, NULL, NULL, "misc/ar1_pkup.wav", "models/items/armor/combat/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "i_combatarmor",
     /* pickup */ "Combat Armor",
     /* width */ 3, 0, NULL, IT_ARMOR, 0, &combatarmor_info, ARMOR_COMBAT,
     /* precache */ ""},

    /*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_armor_jacket", Pickup_Armor, NULL, NULL, NULL, "misc/ar1_pkup.wav", "models/items/armor/jacket/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "i_jacketarmor",
     /* pickup */ "Jacket Armor",
     /* width */ 3, 0, NULL, IT_ARMOR, 0, &jacketarmor_info, ARMOR_JACKET,
     /* precache */ ""},

    /*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_armor_shard", Pickup_Armor, NULL, NULL, NULL, "misc/ar2_pkup.wav", "models/items/armor/shard/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "i_jacketarmor",
     /* pickup */ "Armor Shard",
     /* width */ 3, 0, NULL, IT_ARMOR, 0, NULL, ARMOR_SHARD,
     /* precache */ ""},

    /*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_power_screen", Pickup_PowerArmor, Use_PowerArmor, Drop_PowerArmor, NULL, "misc/ar3_pkup.wav",
     "models/items/armor/screen/tris.md2", EF_ROTATE, NULL,
     /* icon */ "i_powerscreen",
     /* pickup */ "Power Screen",
     /* width */ 0, 60, NULL, IT_ARMOR, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_power_shield", Pickup_PowerArmor, Use_PowerArmor, Drop_PowerArmor, NULL, "misc/ar3_pkup.wav",
     "models/items/armor/shield/tris.md2", EF_ROTATE, NULL,
     /* icon */ "i_powershield",
     /* pickup */ "Power Shield",
     /* width */ 0, 60, NULL, IT_ARMOR, 0, NULL, 0,
     /* precache */ "misc/power2.wav misc/power1.wav"},

    //
    // WEAPONS
    //

    /* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
    always owned, never in the world
    */
    {"weapon_blaster", NULL, Use_Weapon, NULL, Weapon_Blaster, "misc/w_pkup.wav", NULL, 0,
     "models/weapons/v_blast/tris.md2",
     /* icon */ "w_blaster",
     /* pickup */ "Blaster", 0, 0, NULL, IT_WEAPON | IT_STAY_COOP, WEAP_BLASTER, NULL, 0,
     /* precache */ "weapons/blastf1a.wav misc/lasfly.wav"},

    /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_shotgun", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_Shotgun, "misc/w_pkup.wav",
     "models/weapons/g_shotg/tris.md2", EF_ROTATE, "models/weapons/v_shotg/tris.md2",
     /* icon */ "w_shotgun",
     /* pickup */ "Shotgun", 0, 1, "Shells", IT_WEAPON | IT_STAY_COOP, WEAP_SHOTGUN, NULL, 0,
     /* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav"},

    /*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_supershotgun", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_SuperShotgun, "misc/w_pkup.wav",
     "models/weapons/g_shotg2/tris.md2", EF_ROTATE, "models/weapons/v_shotg2/tris.md2",
     /* icon */ "w_sshotgun",
     /* pickup */ "Super Shotgun", 0, 2, "Shells", IT_WEAPON | IT_STAY_COOP, WEAP_SUPERSHOTGUN, NULL, 0,
     /* precache */ "weapons/sshotf1b.wav"},

    /*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_machinegun", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_Machinegun, "misc/w_pkup.wav",
     "models/weapons/g_machn/tris.md2", EF_ROTATE, "models/weapons/v_machn/tris.md2",
     /* icon */ "w_machinegun",
     /* pickup */ "Machinegun", 0, 1, "Bullets", IT_WEAPON | IT_STAY_COOP, WEAP_MACHINEGUN, NULL, 0,
     /* precache */
     "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"},

    /*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_chaingun", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_Chaingun, "misc/w_pkup.wav",
     "models/weapons/g_chain/tris.md2", EF_ROTATE, "models/weapons/v_chain/tris.md2",
     /* icon */ "w_chaingun",
     /* pickup */ "Chaingun", 0, 1, "Bullets", IT_WEAPON | IT_STAY_COOP, WEAP_CHAINGUN, NULL, 0,
     /* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"},

    /*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_grenades", Pickup_Ammo, Use_Weapon, Drop_Ammo, Weapon_Grenade, "misc/am_pkup.wav",
     "models/items/ammo/grenades/medium/tris.md2", 0, "models/weapons/v_handgr/tris.md2",
     /* icon */ "a_grenades",
     /* pickup */ "Grenades",
     /* width */ 3, 5, "grenades", IT_AMMO | IT_WEAPON, WEAP_GRENADES, NULL, AMMO_GRENADES,
     /* precache */
     "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "},

    /*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_grenadelauncher", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_GrenadeLauncher, "misc/w_pkup.wav",
     "models/weapons/g_launch/tris.md2", EF_ROTATE, "models/weapons/v_launch/tris.md2",
     /* icon */ "w_glauncher",
     /* pickup */ "Grenade Launcher", 0, 1, "Grenades", IT_WEAPON | IT_STAY_COOP, WEAP_GRENADELAUNCHER, NULL, 0,
     /* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"},

    /*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_rocketlauncher", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_RocketLauncher, "misc/w_pkup.wav",
     "models/weapons/g_rocket/tris.md2", EF_ROTATE, "models/weapons/v_rocket/tris.md2",
     /* icon */ "w_rlauncher",
     /* pickup */ "Rocket Launcher", 0, 1, "Rockets", IT_WEAPON | IT_STAY_COOP, WEAP_ROCKETLAUNCHER, NULL, 0,
     /* precache */
     "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav "
     "models/objects/debris2/tris.md2"},

    /*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_hyperblaster", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_HyperBlaster, "misc/w_pkup.wav",
     "models/weapons/g_hyperb/tris.md2", EF_ROTATE, "models/weapons/v_hyperb/tris.md2",
     /* icon */ "w_hyperblaster",
     /* pickup */ "HyperBlaster", 0, 1, "Cells", IT_WEAPON | IT_STAY_COOP, WEAP_HYPERBLASTER, NULL, 0,
     /* precache */
     "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"},

    /*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_railgun", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_Railgun, "misc/w_pkup.wav",
     "models/weapons/g_rail/tris.md2", EF_ROTATE, "models/weapons/v_rail/tris.md2",
     /* icon */ "w_railgun",
     /* pickup */ "Railgun", 0, 1, "Slugs", IT_WEAPON | IT_STAY_COOP, WEAP_RAILGUN, NULL, 0,
     /* precache */ "weapons/rg_hum.wav"},

    /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"weapon_bfg", Pickup_Weapon, Use_Weapon, Drop_Weapon, Weapon_BFG, "misc/w_pkup.wav",
     "models/weapons/g_bfg/tris.md2", EF_ROTATE, "models/weapons/v_bfg/tris.md2",
     /* icon */ "w_bfg",
     /* pickup */ "BFG10K", 0, 50, "Cells", IT_WEAPON | IT_STAY_COOP, WEAP_BFG, NULL, 0,
     /* precache */
     "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav "
     "weapons/bfg__x1b.wav weapons/bfg_hum.wav"},

    //
    // AMMO ITEMS
    //

    /*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_shells", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav", "models/items/ammo/shells/medium/tris.md2",
     0, NULL,
     /* icon */ "a_shells",
     /* pickup */ "Shells",
     /* width */ 3, 10, NULL, IT_AMMO, 0, NULL, AMMO_SHELLS,
     /* precache */ ""},

    /*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_bullets", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav",
     "models/items/ammo/bullets/medium/tris.md2", 0, NULL,
     /* icon */ "a_bullets",
     /* pickup */ "Bullets",
     /* width */ 3, 50, NULL, IT_AMMO, 0, NULL, AMMO_BULLETS,
     /* precache */ ""},

    /*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_cells", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav", "models/items/ammo/cells/medium/tris.md2", 0,
     NULL,
     /* icon */ "a_cells",
     /* pickup */ "Cells",
     /* width */ 3, 50, NULL, IT_AMMO, 0, NULL, AMMO_CELLS,
     /* precache */ ""},

    /*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_rockets", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav",
     "models/items/ammo/rockets/medium/tris.md2", 0, NULL,
     /* icon */ "a_rockets",
     /* pickup */ "Rockets",
     /* width */ 3, 5, NULL, IT_AMMO, 0, NULL, AMMO_ROCKETS,
     /* precache */ ""},

    /*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"ammo_slugs", Pickup_Ammo, NULL, Drop_Ammo, NULL, "misc/am_pkup.wav", "models/items/ammo/slugs/medium/tris.md2", 0,
     NULL,
     /* icon */ "a_slugs",
     /* pickup */ "Slugs",
     /* width */ 3, 10, NULL, IT_AMMO, 0, NULL, AMMO_SLUGS,
     /* precache */ ""},

    //
    // POWERUP ITEMS
    //
    /*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_quad", Pickup_Powerup, Use_Quad, Drop_General, NULL, "items/pkup.wav", "models/items/quaddama/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "p_quad",
     /* pickup */ "Quad Damage",
     /* width */ 2, 60, NULL, IT_POWERUP, 0, NULL, 0,
     /* precache */ "items/damage.wav items/damage2.wav items/damage3.wav"},

    /*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_invulnerability", Pickup_Powerup, Use_Invulnerability, Drop_General, NULL, "items/pkup.wav",
     "models/items/invulner/tris.md2", EF_ROTATE, NULL,
     /* icon */ "p_invulnerability",
     /* pickup */ "Invulnerability",
     /* width */ 2, 300, NULL, IT_POWERUP, 0, NULL, 0,
     /* precache */ "items/protect.wav items/protect2.wav items/protect4.wav"},

    /*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_silencer", Pickup_Powerup, Use_Silencer, Drop_General, NULL, "items/pkup.wav",
     "models/items/silencer/tris.md2", EF_ROTATE, NULL,
     /* icon */ "p_silencer",
     /* pickup */ "Silencer",
     /* width */ 2, 60, NULL, IT_POWERUP, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_breather", Pickup_Powerup, Use_Breather, Drop_General, NULL, "items/pkup.wav",
     "models/items/breather/tris.md2", EF_ROTATE, NULL,
     /* icon */ "p_rebreather",
     /* pickup */ "Rebreather",
     /* width */ 2, 60, NULL, IT_STAY_COOP | IT_POWERUP, 0, NULL, 0,
     /* precache */ "items/airout.wav"},

    /*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_enviro", Pickup_Powerup, Use_Envirosuit, Drop_General, NULL, "items/pkup.wav",
     "models/items/enviro/tris.md2", EF_ROTATE, NULL,
     /* icon */ "p_envirosuit",
     /* pickup */ "Environment Suit",
     /* width */ 2, 60, NULL, IT_STAY_COOP | IT_POWERUP, 0, NULL, 0,
     /* precache */ "items/airout.wav"},

    /*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16)
    Special item that gives +2 to maximum health
    */
    {"item_ancient_head", Pickup_AncientHead, NULL, NULL, NULL, "items/pkup.wav", "models/items/c_head/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "i_fixme",
     /* pickup */ "Ancient Head",
     /* width */ 2, 60, NULL, 0, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16)
    gives +1 to maximum health
    */
    {"item_adrenaline", Pickup_Adrenaline, NULL, NULL, NULL, "items/pkup.wav", "models/items/adrenal/tris.md2",
     EF_ROTATE, NULL,
     /* icon */ "p_adrenaline",
     /* pickup */ "Adrenaline",
     /* width */ 2, 60, NULL, 0, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_bandolier", Pickup_Bandolier, NULL, NULL, NULL, "items/pkup.wav", "models/items/band/tris.md2", EF_ROTATE,
     NULL,
     /* icon */ "p_bandolier",
     /* pickup */ "Bandolier",
     /* width */ 2, 60, NULL, 0, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
     */
    {"item_pack", Pickup_Pack, NULL, NULL, NULL, "items/pkup.wav", "models/items/pack/tris.md2", EF_ROTATE, NULL,
     /* icon */ "i_pack",
     /* pickup */ "Ammo Pack",
     /* width */ 2, 180, NULL, 0, 0, NULL, 0,
     /* precache */ ""},

    //
    // KEYS
    //
    /*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16)
    key for computer centers
    */
    {"key_data_cd", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/data_cd/tris.md2",
     EF_ROTATE, NULL, "k_datacd", "Data CD", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
    warehouse circuits
    */
    {"key_power_cube", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/power/tris.md2",
     EF_ROTATE, NULL, "k_powercube", "Power Cube", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16)
    key for the entrance of jail3
    */
    {"key_pyramid", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/pyramid/tris.md2",
     EF_ROTATE, NULL, "k_pyramid", "Pyramid Key", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16)
    key for the city computer
    */
    {"key_data_spinner", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/spinner/tris.md2",
     EF_ROTATE, NULL, "k_dataspin", "Data Spinner", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16)
    security pass for the security level
    */
    {"key_pass", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/pass/tris.md2", EF_ROTATE,
     NULL, "k_security", "Security Pass", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16)
    normal door key - blue
    */
    {"key_blue_key", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/key/tris.md2",
     EF_ROTATE, NULL, "k_bluekey", "Blue Key", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16)
    normal door key - red
    */
    {"key_red_key", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav", "models/items/keys/red_key/tris.md2",
     EF_ROTATE, NULL, "k_redkey", "Red Key", 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
    tank commander's head
    */
    {"key_commander_head", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav",
     "models/monsters/commandr/head/tris.md2", EF_GIB, NULL,
     /* icon */ "k_comhead",
     /* pickup */ "Commander's Head",
     /* width */ 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    /*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16)
    tank commander's head
    */
    {"key_airstrike_target", Pickup_Key, NULL, Drop_General, NULL, "items/pkup.wav",
     "models/items/keys/target/tris.md2", EF_ROTATE, NULL,
     /* icon */ "i_airstrike",
     /* pickup */ "Airstrike Marker",
     /* width */ 2, 0, NULL, IT_STAY_COOP | IT_KEY, 0, NULL, 0,
     /* precache */ ""},

    {NULL, Pickup_Health, NULL, NULL, NULL, "items/pkup.wav", NULL, 0, NULL,
     /* icon */ "i_health",
     /* pickup */ "Health",
     /* width */ 3, 0, NULL, 0, 0, NULL, 0,
     /* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"},

    // end of list marker
    {NULL}};

/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
 */
void SP_item_health(edict_t *self) {
  if(deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH)) {
    G_FreeEdict(self);
    return;
  }

  self->model = "models/items/healing/medium/tris.md2";
  self->count = 10;
  SpawnItem(self, FindItem("Health"));
  gi.soundindex("items/n_health.wav");
}

/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16)
 */
void SP_item_health_small(edict_t *self) {
  if(deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH)) {
    G_FreeEdict(self);
    return;
  }

  self->model = "models/items/healing/stimpack/tris.md2";
  self->count = 2;
  SpawnItem(self, FindItem("Health"));
  self->style = HEALTH_IGNORE_MAX;
  gi.soundindex("items/s_health.wav");
}

/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16)
 */
void SP_item_health_large(edict_t *self) {
  if(deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH)) {
    G_FreeEdict(self);
    return;
  }

  self->model = "models/items/healing/large/tris.md2";
  self->count = 25;
  SpawnItem(self, FindItem("Health"));
  gi.soundindex("items/l_health.wav");
}

/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16)
 */
void SP_item_health_mega(edict_t *self) {
  if(deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH)) {
    G_FreeEdict(self);
    return;
  }

  self->model = "models/items/mega_h/tris.md2";
  self->count = 100;
  SpawnItem(self, FindItem("Health"));
  gi.soundindex("items/m_health.wav");
  self->style = HEALTH_IGNORE_MAX | HEALTH_TIMED;
}

void InitItems(void) { game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1; }

/*
===============
SetItemNames

Called by worldspawn
===============
*/
void SetItemNames(void) {
  int i;
  gitem_t *it;

  for(i = 0; i < game.num_items; i++) {
    it = &itemlist[i];
    gi.configstring(CS_ITEMS + i, it->pickup_name);
  }

  jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
  combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
  body_armor_index = ITEM_INDEX(FindItem("Body Armor"));
  power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
  power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
}