/* Copyright (C) 2006 Parallel Realities 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 "items.h" void drawItem(bool transparent, bool reflected) { if (transparent) { return; } glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); glPushMatrix(); { glColor4fv(self->color.color); glTranslatef(self->position.x, self->position.y, self->position.z); if (reflected) { glTranslatef(0, 0, -(self->heightAboveGround * 2)); } glRotatef(90.0, 1.0, 0.0, 0.0); glRotatef(self->rotation.x, 1.0, 0.0, 0.0); glRotatef(self->rotation.y, 0.0, 1.0, 0.0); glRotatef(self->rotation.z, 0.0, 0.0, 1.0); if (self->definition->type != ITM_STICKY_C4) { self->rotation.y += engine->getTimeDifference(TD_ANIMATION); } glScalef(self->scale, self->scale, self->scale); glBindTexture(GL_TEXTURE_2D, self->definition->texture[0]->data); glCallList(self->definition->model[0]->data); } glPopMatrix(); } void stickyC4Countdown() { self->custom--; self->thinkTime = 23; if (self->owner != NULL) { Math::limit(&self->custom, 0, 25); } else { Math::wrap(&self->custom, 0, 25); } if (((int)self->custom % 2) == 0) { self->color = GLColor::grey; } else { self->color = GLColor::lightGrey; } } void throwSpawnedItem(Entity *item) { item->velocity.x = Math::rrand(-8, 8); item->velocity.y = Math::rrand(-8, 8); item->velocity.z = Math::rrand(5, 10); item->velocity = item->velocity * 0.1; } int handleMedicalItem(Unit *unit, Item *item, bool pickupNow) { if ((unit->health == unit->maxHealth) && (!(item->flags & EF_AUTO_PICKUP))) { return ITEM_LEAVE; } if (!pickupNow) { return ITEM_PICKUP; } if (unit == player) { game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), ((ItemDef*)item->definition)->getName()); game->alterStat(_("Cherries Picked Up"), 1); } audio->playSound(SND_GULP, CH_ANY, 128); unit->health += item->itemPower; Math::limit(&unit->health, 0, unit->maxHealth); return ITEM_CONSUME; } int handlePowerUp(Item *item, bool pickupNow) { if (!pickupNow) { return ITEM_PICKUP; } WeaponDef *weaponDef; ItemDef *itemDef = (ItemDef*)item->definition; Unit *teeka; int weaponType; switch (itemDef->type) { case ITM_SHIELD_BELT: player->maxShield = max(player->maxShield, item->itemPower); game->setInfoMessage(INFO_PICKUP, _("Picked up a Shield Belt (%dVGhz)"), item->itemPower); addPowerUpParticles(player->position); break; case ITM_TEEKA_WEAPON: weaponType = engine->getValueOfDefine(item->getName()); game->teekaWeapon = weaponType; weaponDef = &entityManager->weaponDef[weaponType]; teeka = findTeeka(); if (teeka != NULL) { teeka->currentWeapon = weaponDef; addPowerUpParticles(teeka->position); } game->setInfoMessage(INFO_PICKUP, _("Picked up %s for Teeka"), weaponDef->getName()); break; } //audio->playSound(SND_POWERUP, CH_ANY); audio->playSound(SND_ITEM, CH_ANY, 128); game->alterStat(_("Items Picked Up"), 1); return ITEM_CONSUME; } int handleSpecialItem(Item *item, bool pickupNow) { // Player must always pick these up themselves... if (!pickupNow) { return ITEM_LEAVE; } ItemDef *itemDef = (ItemDef*)item->definition; int sameItemSlot = (!itemDef->collatable) ? -1 : game->getItemSlot(itemDef->getName()); int freeSlot = game->getFreeInventorySlot(); if (itemDef->type == ITM_GRAPPLE_HOOK) { game->dropGrappleHooks = true; } if (sameItemSlot == -1) { if (itemDef->type < ITM_CUSTOM_FIRST) { if ((freeSlot == -1) && (!(item->flags & EF_AUTO_PICKUP))) { return ITEM_LEAVE; } } else if (itemDef->customCarriable) { if (freeSlot == -1) { return ITEM_LEAVE; } } } else { // can't carry more than five at a time... if (game->item[sameItemSlot]->definition->type != ITM_TELETAG) { if (game->item[sameItemSlot]->itemPower >= 5) { game->setGamePlayMessage(INFO_GENERAL, _("Cannot carry any more %ss"), itemDef->getName()); return ITEM_LEAVE; } } game->item[sameItemSlot]->itemPower += item->itemPower; String s; s.setText("str_item_%d", sameItemSlot); textureManager->removeTexture(s.getText()); } audio->playSound(SND_ITEM, CH_ANY, 128); char *translatedName = (itemDef->type < ITM_CUSTOM_FIRST) ? itemDef->getName() : itemDef->getName(); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), translatedName); game->alterStat(_("Items Picked Up"), 1); if (sameItemSlot == -1) { if ((itemDef->type < ITM_CUSTOM_FIRST) || (itemDef->customCarriable)) { item->setOwner(player); game->item[freeSlot] = item; return ITEM_PICKUP; } } return ITEM_CONSUME; } int handleExplosiveWeapon(Weapon *weapon, bool pickupNow, WeaponDef *weaponDef) { // Galdov can do this in the last battle! if (game->weapon[3] == NULL) { weapon->setOwner(player); game->weapon[3] = weapon; audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), weaponDef->getName()); return ITEM_PICKUP; } int rtn = ITEM_LEAVE; int clipSize = ((WeaponDef*)game->weapon[3]->definition)->clipSize; // we're holding grenades. Replace with this weapon... if ((game->weapon[3]->definition->type == WEP_GRENADE) && (weaponDef->type != WEP_GRENADE)) { if (!pickupNow) { return ITEM_PICKUP; } weapon->setOwner(player); weapon->currentAmmo += game->weapon[3]->currentAmmo; // we need to destroy the grenades... entityManager->weaponList.remove(game->weapon[3]); game->weapon[3] = weapon; //game->selectedWeapon = weapon; audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), weaponDef->getName()); rtn = ITEM_PICKUP; } else if (weaponDef->type == WEP_GRENADE) { if (game->weapon[3]->currentAmmo < clipSize) { if (!pickupNow) { return ITEM_PICKUP; } game->weapon[3]->currentAmmo += weapon->currentAmmo; Math::limit(&game->weapon[3]->currentAmmo, 0, clipSize); audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up some %s"), weaponDef->getName()); rtn = ITEM_CONSUME; } else if (weapon->flags & EF_AUTO_PICKUP) { audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); return ITEM_CONSUME; } } else { if (game->weapon[3]->definition->type == weapon->definition->type) { if (game->weapon[3]->currentAmmo < clipSize) { if (!pickupNow) { return ITEM_PICKUP; } game->weapon[3]->currentAmmo += weapon->currentAmmo; Math::limit(&game->weapon[3]->currentAmmo, 0, clipSize); audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up some %s"), weaponDef->getName()); rtn = ITEM_CONSUME; } } else { if (!pickupNow) { return ITEM_LEAVE; } if (weapon->flags & EF_AUTO_PICKUP) { audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); return ITEM_CONSUME; } game->setGamePlayMessage(INFO_GENERAL, _("Press ACTION to swap %s for %s"), game->selectedWeapon->definition->getName(), weaponDef->getName()); if (game->isControl(CTRL_ACTION)) { game->resetControl(CTRL_ACTION); int ammo = game->weapon[3]->currentAmmo; // put first weapon back on map... game->weapon[3]->position = player->position; game->weapon[3]->custom = 100; game->weapon[3]->setOwner(NULL); throwSpawnedItem(game->weapon[3]); // get second weapon game->weapon[3] = weapon; game->weapon[3]->setOwner(player); game->weapon[3]->currentAmmo = ammo; game->selectedWeapon = game->weapon[3]; audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), weaponDef->getName()); String s; s.setText("str_weapon_%d", 3); textureManager->removeTexture(s.getText()); textureManager->removeTexture("str_current_ammo"); rtn = ITEM_PICKUP; } } } if (rtn != ITEM_LEAVE) { game->alterStat(_("Weapons Picked Up"), 1); String s; s.setText("str_weapon_%d", 3); textureManager->removeTexture(s.getText()); textureManager->removeTexture("str_current_ammo"); } return rtn; } int handleNormalWeapon(Weapon *weapon, bool pickupNow, WeaponDef *weaponDef) { int freeSlot = -1; int currentWeaponSlot = -1; for (int i = 3 ; i > -1 ; i--) { if (game->weapon[i] == NULL) { freeSlot = i; } else if (game->weapon[i]->definition == weapon->definition) { currentWeaponSlot = i; } } // Does not have the weapon and has a free slot if ((currentWeaponSlot == -1) && (freeSlot != -1)) { if (!pickupNow) { return ITEM_PICKUP; } weapon->setOwner(player); game->weapon[freeSlot] = weapon; game->weapon[freeSlot]->currentAmmo = weapon->currentAmmo; game->selectedWeapon = game->weapon[freeSlot]; game->weaponIndex = freeSlot; audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), weaponDef->getName()); game->alterStat(_("Weapons Picked Up"), 1); return ITEM_PICKUP; } else if (currentWeaponSlot != -1) { if (game->weapon[currentWeaponSlot]->currentAmmo == weaponDef->clipSize) { if (weapon->flags & EF_AUTO_PICKUP) { audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); return ITEM_CONSUME; } return ITEM_LEAVE; } if (!pickupNow) { return ITEM_PICKUP; } // auto switch to better weapon if not underwater if (!(player->contentType & CONTENTS_WATER)) { if ((game->selectedWeapon == game->weapon[0]) && (game->weapon[currentWeaponSlot]->currentAmmo == 0)) { game->selectedWeapon = game->weapon[currentWeaponSlot]; game->weaponIndex = currentWeaponSlot; } } game->weapon[currentWeaponSlot]->currentAmmo += weapon->currentAmmo; Math::limit(&game->weapon[currentWeaponSlot]->currentAmmo, 0, weaponDef->clipSize); audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); if (weaponDef->type != WEP_GRENADE) { game->setInfoMessage(INFO_PICKUP, _("Got some %s ammo"), weaponDef->getName()); } else { game->setInfoMessage(INFO_PICKUP, _("Picked up some %s"), weaponDef->getName()); } game->alterStat(_("Weapons Picked Up"), 1); String s; s.setText("str_weapon_%d", currentWeaponSlot); textureManager->removeTexture(s.getText()); textureManager->removeTexture("str_current_ammo"); return ITEM_CONSUME; } else if ((freeSlot == -1) && (game->selectedWeapon != game->weapon[0]) && (game->selectedWeapon != game->weapon[3])) { if (!pickupNow) { return ITEM_LEAVE; } if (weapon->flags & EF_AUTO_PICKUP) { audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); return ITEM_CONSUME; } game->setGamePlayMessage(INFO_GENERAL, _("Press ACTION to swap %s for %s"), game->selectedWeapon->definition->getName(), weaponDef->getName()); if (game->isControl(CTRL_ACTION)) { game->resetControl(CTRL_ACTION); int i = game->getSelectedWeaponSlot(); // put first weapon back on map... game->weapon[i]->position = player->position; game->weapon[i]->custom = 100; game->weapon[i]->setOwner(NULL); throwSpawnedItem(game->weapon[i]); /* if (game->weapon[i]->currentAmmo <= 0) { game->weapon[i]->health = -100; } */ // get second weapon game->weapon[i] = weapon; game->weapon[i]->setOwner(player); game->selectedWeapon = game->weapon[i]; audio->playSound(SND_PICKUP_WEAPON, CH_PICKUP, 128); game->setInfoMessage(INFO_PICKUP, _("Picked up %s"), weaponDef->getName()); String s; s.setText("str_weapon_%d", i); textureManager->removeTexture(s.getText()); textureManager->removeTexture("str_current_ammo"); return ITEM_PICKUP; } } return ITEM_LEAVE; } int handleWeaponItem(Weapon *weapon, bool pickupNow) { WeaponDef *weaponDef = (WeaponDef*)weapon->definition; if (!(weaponDef->flags & EF_EXPLODES)) { return handleNormalWeapon(weapon, pickupNow, weaponDef); } else { return handleExplosiveWeapon(weapon, pickupNow, weaponDef); } return ITEM_LEAVE; } int pickUpItem(Unit *unit, Entity *ent, bool pickupNow) { if (ent->entityType == ET_ITEM) { Item *item = (Item*)ent; ItemDef *itemDef = (ItemDef*)item->definition; switch (itemDef->itemType) { case IT_MEDICAL: return handleMedicalItem(unit, item, pickupNow); break; case IT_POWER_UP: if (unit == player) { return handlePowerUp(item, pickupNow); } return ITEM_LEAVE; break; case IT_SPECIAL: if (unit == player) { return handleSpecialItem(item, pickupNow); } return ITEM_LEAVE; break; } } else if (unit == player) { return handleWeaponItem((Weapon*)ent, pickupNow); } return ITEM_LEAVE; } void pickupItem(Entity *other) { if (other == NULL) { return; } if ((self->custom > 0) || (other->health <= 0) || (other->entityType != ET_BLOB)) { return; } int result = pickUpItem((Unit*)other, self, true); if (result != ITEM_LEAVE) { if (self->name != "") { if (self->definition->type < ITM_CUSTOM_FIRST) { fireTriggers(self->getName(), TRIGGER_TYPE_ITEM_PICKED_UP); } else { fireTriggers(self->definition->getName(), TRIGGER_TYPE_ITEM_PICKED_UP); } } debug(("%s picked up %s\n", other->getName(), self->definition->getName())); // picked up an item that can be removed (eg - cherry or ammo) if (result == ITEM_CONSUME) { self->health = 0; self->touch = 0; if (other != player) { debug(("%s resuming normal function\n", other->getName())); other->action = other->walk; other->custom = 0; other->velocity.x = other->velocity.y = 0; other->thinkTime = 0; } } } } void addCustomItem(Properties *props) { debug(("addCustomItem()\n")); if (!props->hasProperty("name")) { printf("ERROR: Custom items MUST have a name!\n"); exit(1); } ItemDef *def = NULL; int i = ITM_CUSTOM_FIRST; while (def == NULL) { if (entityManager->itemDef[i].name == "") { def = &entityManager->itemDef[i]; break; } i++; if (i > ITM_CUSTOM_LAST) { printf("ERROR: No more free custom item slots (Maximum of %d)\n", (ITM_CUSTOM_LAST + 1) - ITM_CUSTOM_FIRST); exit(1); } } def->type = i; def->itemPower = 1; def->itemType = IT_SPECIAL; def->name = props->getString("name", "???"); def->modelName[0] = props->getString("model1", "NULL"); def->modelName[1] = props->getString("model2", "NULL"); def->textureName[0] = props->getString("texture1", "NULL"); def->textureName[1] = props->getString("texture2", "NULL"); def->bbMins = props->getVector("bbMins"); def->bbMaxs = props->getVector("bbMaxs"); def->customCarriable = props->getInt("customCarriable", 0); debug(("Added custom item '%s'\n", def->name.getText())); } Item *addStickyC4(Vector position) { Item *item = entityManager->spawnItem(ITM_STICKY_C4); item->position = position; item->custom = 20; item->thinkTime = 25; item->draw = drawItem; item->action = &stickyC4Countdown; item->flags = EF_WEIGHTLESS+EF_NOCLIP; return item; } Item *addItem(Properties *props) { #if !DEV if (props->hasProperty("test")) { return NULL; } #endif int type = entityManager->getEntityType(props); if (type == -1) { printf("ERROR: addItem() - Type not known!!\n"); exit(1); } Item *item = entityManager->spawnItem(type); item->draw = drawItem; item->load(props); if (!game->allStatic) { item->touch = &pickupItem; } if (item->definition->type == ITM_STICKY_C4) { item->touch = NULL; item->action = &stickyC4Countdown; item->custom = 5; } debug(("addItem() - %s [%d, %s]\n", item->definition->getName(), item->getUniqueId(), item->position.toString())); return item; } Weapon *addWeapon(Properties *props) { #if !DEV if (props->hasProperty("test")) { return NULL; } #endif int type = entityManager->getEntityType(props); if (type == -1) { printf("ERROR: addWeapon() - Type not known!!\n"); exit(1); } Weapon *weapon = entityManager->spawnWeapon(type, false); weapon->draw = drawItem; weapon->load(props); if (!game->allStatic) { weapon->touch = &pickupItem; } debug(("addWeapon() - %s [%d, %s]\n", weapon->definition->getName(), weapon->getUniqueId(), weapon->position.toString())); return weapon; } void addHelperItems(int numberOfWeapons, WeaponDef *weaponDef, Vector position) { Properties weaponProps, itemProps; Weapon *weapon; float clipSize; int minAmmo, maxAmmo; weaponProps.setProperty("position", position); itemProps.setProperty("position", position); if (weaponDef != NULL) { for (int i = 0 ; i < numberOfWeapons ; i++) { weaponProps.setProperty("classname", weaponDef->type); weapon = addWeapon(&weaponProps); clipSize = weaponDef->clipSize; clipSize *= (mission->enemyLevel * 0.65); minAmmo = (int)max(1, (clipSize * 0.10)); maxAmmo = (int)max(2, (clipSize * 0.15)); weapon->currentAmmo = Math::rrand(minAmmo, maxAmmo); throwSpawnedItem(weapon); } } if ((rand() % 8) == 0) { weaponProps.setProperty("classname", "WEP_GRENADE"); weapon = addWeapon(&weaponProps); weapon->currentAmmo = 1; throwSpawnedItem(weapon); } int r = rand() % getAdjustedHealthChance(5, 1); debug(("Cherry chance: %d\n", r)); if (r == 0) { itemProps.setProperty("classname", "ITM_CHERRY"); Item *item = addItem(&itemProps); item->itemPower = Math::rrand(1, item->itemPower); throwSpawnedItem(item); } r = rand() % getAdjustedHealthChance(25, 2); debug(("Medipack chance: %d\n", r)); if (r == 0) { itemProps.setProperty("classname", "ITM_MEDIPACK"); Item *item = addItem(&itemProps); item->itemPower = Math::rrand(10, item->itemPower); throwSpawnedItem(item); } if (game->dropGrappleHooks) { int slot = game->getItemSlot(_("Grappling Hook")); int currentNumber = 0; if (slot != -1) { currentNumber = game->item[slot]->itemPower; } if (currentNumber != 5) { r = rand() % (2 + (2 * currentNumber)); debug(("Grappling Hook chance: %d\n", r)); if (r == 0) { itemProps.setProperty("classname", "ITM_GRAPPLE_HOOK"); Item *item = addItem(&itemProps); item->itemPower = 1; item->position.z += 5; throwSpawnedItem(item); } } else { debug(("Not dropping grappling hooks - Player already has 5\n")); } } else { debug(("Not dropping grappling hooks\n")); } } void bringItemToPlayer(Entity *item) { if ((player->health <= 0) || (player->helpless)) { return; } /* * First check that we're allowed to pick up this item. * If not then just leave it there. */ if (!(item->flags & EF_AUTO_PICKUP)) { if (pickUpItem(player, item, false) == ITEM_LEAVE) { return; } } item->velocity = player->position; item->velocity -= item->position; item->velocity.normalize(); item->velocity = item->velocity * 2; item->thinkTime = Math::rrand(1, 3); } void processItemList(List *list) { for (Entity *item = (Entity*)list->getFirstElement() ; item != NULL ; item = (Entity*)item->next) { self = item; bbManager->removeBox(item); if (item->owner != NULL) { continue; } if (mission->itemLimit > 0) { if (list->getSize() > mission->itemLimit) { item->health = 0; } } if (item->health <= 0) { debug(("Removing Item '%s'\n", item->definition->getName())); bbManager->destroyBox(item); item = (Entity*)item->previous; list->remove(item->next); continue; } if (item->definition->type != ITM_STICKY_C4) { Math::limit(&(--item->custom), 0, RAND_MAX); } Math::limit(&(item->thinkTime -= engine->getTimeDifference(TD_LOGIC)), 0, RAND_MAX); if (item->thinkTime == 0) { if (item->action != NULL) { item->action(); } } if (withinViewableRange(item)) { entityManager->addEntityToDrawList(item); } item->applyGravity(engine->getTimeDifference(TD_LOGIC)); moveEntity(item); if (item->flags & EF_ONGROUND) { item->flags &= ~EF_BOUNCES; /* if (!(item->flags & EF_BOUNCES)) { item->velocity.z = 0; } else if ((item->riding == NULL) && (fabs(item->velocity.z) < EPSILON)) { item->flags &= ~EF_BOUNCES; } */ } for (int i = 0 ; i < engine->getInertiaRate() ; i++) { item->velocity.x *= 0.95; item->velocity.y *= 0.95; } if (item->contentType & CONTENTS_WATER) { item->flags &= ~EF_BOUNCES; Math::limit(&item->velocity.z, -0.25, 0.25); } else if ((item->contentType & CONTENTS_LAVA) || (item->contentType & CONTENTS_SLIME)) { item->flags |= EF_AUTO_PICKUP; item->flags |= EF_WEIGHTLESS; item->flags |= EF_NOCLIP; item->thinkTime = 0; } if (!game->allStatic) { if ((item->flags & EF_AUTO_PICKUP) && (item->thinkTime <= 0) && (game->missionStatus == MISSION_INPROGRESS)) { bringItemToPlayer(item); } if (Math::getDistance(player->position, item->position) < 25) { bringItemToPlayer(item); } } bbManager->addBox(item); } } void doItems() { glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); processItemList(&entityManager->weaponList); processItemList(&entityManager->itemList); }