/* 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 "blobs.h" void blobFollowOrders(); void blobDoOrderFollow(); int getAdjustedHealthChance(int in, int minimum) { float healthPercent = 100; // help out the player or teeka if they're in trouble... for (Unit *blob = (Unit*)entityManager->blobList.getFirstElement() ; blob != NULL ; blob = (Unit*)blob->next) { healthPercent = min(blob->getHealthPercent(), healthPercent); } debug(("Minimum Health = %.2f\n", healthPercent)); if (healthPercent >= 25) { return in; } in = (int)(in * (healthPercent / 15)); Math::limit(&in, minimum, 100); debug(("getAdjustedHealthChance() - %d (min = %d)\n", in, minimum)); return in; } void blobJumping() { if (self->flags & EF_ONGROUND) { self->action = blobDoOrderFollow; } self->thinkTime = 1; } bool blobJumpUp() { if (!(player->flags & EF_ONGROUND)) { return false; } float zDist = (player->position.z - self->position.z); if ((zDist > 3) && (zDist < 50)) { // are we blocked..? float distance = Math::getDistance(self->oldPosition, self->position); if (distance > 0.1) { return false; } self->velocity = player->position; self->velocity -= self->position; self->velocity.normalize(); self->velocity.z = 4.0 / 3.0; self->action = &blobJumping; self->flags &= ~EF_ONGROUND; return true; } return false; } bool blobJumpGap() { Vector start, end, velocity; bool canMakeJump = false; Unit *unit = (Unit*)self; if (unitIsAtEdge(unit)) { if (fabs(player->position.z - self->position.z) > 12) { return false; } // attempt to jump to next ledge if (unit->flags & EF_ONGROUND) { velocity = player->position; velocity -= unit->position; velocity.normalize(); } canMakeJump = false; start = unit->position; for (int i = 0 ; i < 150 ; i += 6) { start += (velocity * 6); end = start; end.z -= 6; if (!bsp->canSightTarget(start, end)) { canMakeJump = true; break; } if (bbManager->canSightSolid(unit, start, end)) { canMakeJump = true; break; } } if (canMakeJump) { float distance = (Math::getDistance(unit->position, start) / 85); unit->velocity = player->position; unit->velocity -= unit->position; unit->velocity.normalize(); unit->velocity = unit->velocity * distance; unit->velocity.z = distance; Math::limit(&unit->velocity.z, 1.25, 2.5); unit->action = &blobJumping; unit->flags &= ~EF_ONGROUND; } } return canMakeJump; } void blobRushTarget() { Unit *unit = (Unit*)self; unit->velocity = unit->target->position; unit->velocity.x += Math::rrand(-10, 10); unit->velocity.y += Math::rrand(-10, 10); unit->velocity.z += Math::rrand(5, 10); unit->velocity -= unit->position; unit->velocity.normalize(); float speed = Math::rrand(50, 100); speed /= 100; unit->velocity = unit->velocity * speed; faceTarget((Unit*)self); } void blobDodgeTarget() { self->velocity.x = Math::rrand(-2, 2); self->velocity.y = Math::rrand(-2, 2); self->velocity.x /= Math::rrand(7, 10); self->velocity.y /= Math::rrand(7, 10); faceTarget((Unit*)self); } void blobsRunToBob() { if (game->currentOrders != ORDERS_FOLLOW) { return; } float z; for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if (unit == player) { continue; } z = unit->velocity.z; faceLocation(unit, player->position); unit->velocity = player->position; unit->velocity -= unit->position; unit->velocity.normalize(); float speed = 1.0; unit->velocity.x /= speed; unit->velocity.y /= speed; unit->velocity.z = z; unit->thinkTime = 500; } } void blobWander() { Unit *unit = (Unit*)self; unit->velocity.x = Math::rrand(-20, 20); unit->velocity.y = Math::rrand(-20, 20); unit->velocity.x /= 100; unit->velocity.y /= 100; float angle = atan2(unit->velocity.y, unit->velocity.x); unit->rotation.x = 90 + toDegrees(angle); unit->thinkTime = Math::rrand(200, 400); // always try and swim up if (unit->liquidLevel == 2) { unit->velocity.z += (0.01 * engine->getTimeDifference(TD_LOGIC)); unit->thinkTime = Math::rrand(50, 75); } return; } void blobTeleport() { addTeleporterParticle(self->position); if (self->custom == 0) { audio->playSound(SND_TELEPORT1, CH_ANY, camera->getSoundDistance(self->position)); } if (self->custom < 10) { self->rotation.x += self->custom; } else { self->rotation.x += 10; } self->custom += (0.05 * engine->getTimeDifference(TD_LOGIC)); Math::limit(&self->custom, 0, 20); if (self->custom >= 10) { self->flags |= EF_WEIGHTLESS; self->velocity.z = 1; } if (self->custom >= 15) { addTeleportParticles(self->position); self->flags |= EF_EXITTED; if (self->definition->type == BLOB_MIA) { game->setInfoMessage(INFO_GOOD, _("Rescued %s"), self->getName()); fireTriggers(self->getName(), TRIGGER_TYPE_BLOB_RESCUED); fireTriggers("ANY_MIA", TRIGGER_TYPE_BLOB_RESCUED); } } } void blobPreTeleport() { self->custom = 0; self->action = &blobTeleport; } void miaBlobThink() { if (!bsp->canSightTarget(self->position, player->position)) { if ((rand() % 3) == 0) { blobWander(); } else { self->velocity.x = 0; self->velocity.y = 0; self->thinkTime = Math::rrand(200, 400); } return; } self->velocity.x = 0; self->velocity.y = 0; faceLocation((Unit*)self, player->position); float distance = Math::getDistance(self->position, player->position); if (distance <= 10) { int slot = game->getItemSlot(_("Teletag")); if ((slot == -1) && (!game->cheatItems)) { game->setGamePlayMessage(INFO_GENERAL, _("Teletag required")); return; } game->setGamePlayMessage(INFO_GENERAL, _("Press ACTION to attach Teletag")); if (game->isControl(CTRL_ACTION)) { if (!game->cheatItems) { game->item[slot]->itemPower--; if (game->item[slot]->itemPower == 0) { game->removeItem(_("Teletag")); } } String s; s.setText("str_item_%d", slot); textureManager->removeTexture(s.getText()); game->resetControl(CTRL_ACTION); blobPreTeleport(); } } } void assimiatedBlobThink() { Unit *unit = (Unit*)self; unit->helpless = 1; } void teleportOutBlob(char *name) { debug(("teleportOutBlob() - '%s'\n", name)); for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if (unit->name == name) { unit->custom = 0; unit->action = &blobTeleport; unit->thinkTime = 0; } } } void blobFollowBob() { Vector sightPosition; sightPosition = self->position; sightPosition.z += Math::rrand(0, 25); float distance = Math::getDistance(self->position.x, self->position.y, player->position.x, player->position.y); Unit *unit = (Unit*)self; if (!bsp->canSightTarget(sightPosition, player->position)) { // Bob is very close... Try and get to him. if (distance < 500) { self->thinkTime = Math::rrand(1, 5); self->action = blobDoOrderFollow; // try and swim if in water if (unit->liquidLevel == 2) { self->velocity.z = 1.5; } return; } if (rand() % 5 > 0) { self->velocity.x = self->velocity.y = 0; self->thinkTime = Math::rrand(200, 400); self->action = blobDoOrderFollow; // always try and swim up if (unit->liquidLevel == 2) { self->velocity.z += (0.01 * engine->getTimeDifference(TD_LOGIC)); } return; } blobWander(); self->action = blobDoOrderFollow; return; } unit->showHealthData = true; unit->action = &blobFollowOrders; faceLocation((Unit*)self, player->position); if (game->currentOrders != ORDERS_RETREAT) { if (distance < 30) { if (!blobJumpUp()) { self->velocity.x = 0; self->velocity.y = 0; } return; } } float z = self->velocity.z; self->velocity = player->position; self->velocity.x += Math::rrand(-6, 6); self->velocity.y += Math::rrand(-6, 6); self->velocity -= self->position; self->velocity.normalize(); float speed = 8.0 - (distance / 10); if (game->currentOrders != ORDERS_RETREAT) { Math::limit(&speed, 0.8, 3.0); } else { Math::limit(&speed, 0.6, 1.5); } self->thinkTime = Math::rrand(5, 10); self->velocity.x /= speed; self->velocity.y /= speed; self->velocity.z /= speed; if (unit->liquidLevel != 2) { self->velocity.z = z; } blobJumpUp(); blobJumpGap(); } bool blobSightTarget(Unit *unit, int range) { Vector sightPosition; unit->target = NULL; float distance = 0; float targetDistance = range; sightPosition = unit->position; sightPosition.z += Math::rrand(0, 25); for (Unit *enemy = (Unit*)entityManager->enemyList.getFirstElement() ; enemy != NULL ; enemy = (Unit*)enemy->next) { if ((!enemy->canBeDamaged()) ||( enemy->health <= 0) || (enemy->flags & EF_NOTARGET)) { continue; } distance = Math::getDistance(unit->position, enemy->position); if (distance < targetDistance) { if (!unitCanSeeTarget(unit, enemy, sightPosition)) { continue; } targetDistance = distance; unit->target = enemy; } } for (Boss *boss = (Boss*)entityManager->bossList.getFirstElement() ; boss != NULL ; boss = (Boss*)boss->next) { if ((boss->health <= 0) || (boss->entityType == ET_NEUTRAL) || (boss->flags & EF_VANISHED)) { continue; } distance = Math::getDistance(unit->position, boss->position); if (distance < targetDistance) { if (!unitCanSeeTarget(unit, boss, sightPosition)) { continue; } targetDistance = distance; unit->target = boss; } } return (unit->target != NULL); } void goodBlobAttack() { Unit *unit = (Unit*)self; if (unit->target->flags & EF_DEAD) { unit->custom = 0; unit->action = &blobFollowOrders; return; } faceTarget(unit); if (unit->reload > 0) { return; } unit->thinkTime = 0; unit->action = &goodBlobAttack; if (unit->currentWeapon->type == WEP_UZI) { attackTarget(unit); } else { attackTargetLeading(unit); } if ((--unit->custom == 0) || (unit->target->health <= 0)) { unit->thinkTime = Math::rrand(50, 100); unit->action = &blobFollowOrders; } } void goodBlobSetFireRate() { Unit *unit = (Unit*)self; switch (unit->currentWeapon->type) { case WEP_PISTOL: unit->custom = Math::rrand(1, 2); break; case WEP_UZI: unit->custom = Math::rrand(3 + mission->enemyLevel, 5 + mission->enemyLevel); break; case WEP_PLASMA_PISTOL: unit->custom = Math::rrand(2, mission->enemyLevel + 1); break; case WEP_PLASMA_RIFLE: unit->custom = Math::rrand(4, 8); break; default: unit->custom = Math::rrand(1, 2); break; } } void goodBlobPreAttack() { switch (self->definition->type) { case BLOB_HACKER: case BLOB_ENGINEER: case BLOB_DEMOLITION: blobDodgeTarget(); goodBlobSetFireRate(); self->action = goodBlobAttack; break; case BLOB_TEEKA: if ((rand() % 10) == 0) { blobRushTarget(); } else { blobDodgeTarget(); } goodBlobSetFireRate(); self->action = goodBlobAttack; break; default: if ((rand() % 4) == 0) { blobRushTarget(); } else { blobDodgeTarget(); } goodBlobSetFireRate(); self->action = goodBlobAttack; break; } self->thinkTime = Math::rrand(100, 300); } bool blobSightHealth() { if (self->health == self->maxHealth) { return false; } Item *item; ItemDef *itemDef; Item *targetHealth = NULL; float distance; float currentDistance = RAND_MAX; for (Entity *ent = (Entity*)entityManager->itemList.getFirstElement() ; ent != NULL ; ent = (Entity*)ent->next) { if (ent->health <= 0) { continue; } if (fabs(self->position.z - ent->position.z) > 3) { continue; } item = (Item*)ent; itemDef = (ItemDef*)item->definition; if (itemDef->itemType == IT_MEDICAL) { distance = Math::getDistance(self->position.x, self->position.y, item->position.x, item->position.y); if (distance > 150) { continue; } if (unitCanSeeTarget((Unit*)self, ent, self->position)) { if (distance < currentDistance) { currentDistance = distance; targetHealth = item; } } } } for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if (unit->definition->type == BLOB_MIA) { continue; } if (unit == self) { continue; } if (unit->health == unit->maxHealth) { continue; } if ((self->definition->type != BLOB_SOLDIER) && (unit->definition->type == BLOB_SOLDIER)) { continue; } if (unit == player) { if (unit->health < (self->health / 3) && unit->health > 0) { return false; } } else if (unit->health < self->health && unit->health > 0) { return false; } } if (targetHealth != NULL) { float oldZ = self->velocity.z; self->velocity = targetHealth->position; self->velocity -= self->position; self->velocity.normalize(); self->velocity.x *= 0.75; self->velocity.y *= 0.75; self->velocity.z = oldZ; faceLocation((Unit*)self, item->position); debug(("%s is going for health...\n", self->getName())); return true; } return false; } void goodBlobGetItem() { if (--self->custom == 0) { self->action = &blobFollowOrders; } } void blobDoOrderFollow() { Unit *unit = (Unit*)self; // Blobs never attack whilst in water... if (unit->liquidLevel == 2) { unit->action = &blobFollowBob; return; } if (blobSightHealth()) { self->action = &goodBlobGetItem; self->custom = 100; } else if (blobSightTarget(unit, (entityManager->bossList.getSize() == 0) ? PLAYER_TARGET_RANGE : PLAYER_TARGET_RANGE * 2)) { faceTarget(unit); unit->action = &goodBlobPreAttack; } else { unit->action = &blobFollowBob; } } void blobDoOrderWait() { self->velocity.x = 0; self->velocity.y = 0; if (blobSightHealth()) { self->action = &goodBlobGetItem; self->custom = 100; } else if (blobSightTarget((Unit*)self, PLAYER_TARGET_RANGE)) { faceTarget((Unit*)self); self->action = &goodBlobPreAttack; } else { faceLocation((Unit*)self, player->position); } self->thinkTime = Math::rrand(1, 2); } void blobDoOrderRetreat() { self->action = &blobFollowBob; } void blobWait() { self->thinkTime = 450; self->action = &blobFollowOrders; } void resetBlobOrders() { for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if ((unit == player) || (unit->definition->type == BLOB_MIA)) { continue; } if (unit->action != blobFollowOrders) { continue; } if (unit->switchTarget != NULL) { unit->switchTarget->owner = NULL; unit->switchTarget = NULL; } unit->thinkTime = 0; unit->action = &blobFollowOrders; unit->completedOrders = false; } } void blobEngineerUseSwitch() { self->custom -= engine->getTimeDifference(TD_LOGIC); if (self->custom > 0) { return; } Unit *unit = (Unit*)self; unit->action = blobFollowOrders; handleSwitchUse(unit->switchTarget); unit->switchTarget->owner = NULL; unit->switchTarget = NULL; unit->completedOrders = true; unit->thinkTime = 50; } void blobHackerUseSwitch() { self->custom -= engine->getTimeDifference(TD_LOGIC); if (self->custom > 0) { return; } Unit *unit = (Unit*)self; unit->action = blobFollowOrders; Switch *swt = unit->switchTarget; if (completesSwitchPuzzle(swt)) { handleSwitchUse(swt); } swt->owner = NULL; swt = NULL; unit->completedOrders = true; unit->thinkTime = 50; } void blobPlantBomb(Unit *unit) { Vector p = unit->switchTarget->position; p.x += Math::rrand(-15, 15); p.y += Math::rrand(-15, 15); p.z += 2; Item *item = addStickyC4(p); item->rotation.set(-90, 0, Math::rrand(0, 360)); item->scale = 0.75; item->flags = 0; item->custom = rand() % 2; audio->playSound(SND_PLANT_BOMB, CH_ANY, camera->getSoundDistance(unit->position)); } void blobDemolitionUseSwitch2() { Unit *unit = (Unit*)self; unit->action = blobFollowOrders; handleSwitchUse(unit->switchTarget); unit->switchTarget->owner = NULL; unit->switchTarget = NULL; unit->completedOrders = true; unit->thinkTime = 50; } void blobDemolitionUseSwitch1() { String texture; self->custom -= engine->getTimeDifference(TD_LOGIC); if (self->custom > 0) { return; } Unit *unit = (Unit*)self; unit->switchTarget->custom++; unit->custom = 50; texture.setText("textures/game/demolitionPlate%.2d.png", (int)unit->switchTarget->custom); unit->switchTarget->texture[0] = textureManager->getTexture(texture.getText()); if ((((int)unit->switchTarget->custom) % 2) == 0) { blobPlantBomb(unit); } if (unit->switchTarget->custom == 10) { unit->action = blobDemolitionUseSwitch2; } } void blobUseAllocatedSwitch() { Unit *unit = (Unit*)self; self->thinkTime = Math::rrand(1, 3); self->velocity.x = 0; self->velocity.y = 0; // walk the Blob to the feature float z = unit->velocity.z; unit->velocity = unit->switchTarget->position; unit->velocity -= unit->position; unit->velocity.normalize(); unit->velocity = (unit->velocity * 0.85); unit->velocity.z = z; faceLocation(unit, unit->switchTarget->position); if (Math::getDistance(unit->switchTarget->position.x, unit->switchTarget->position.y, unit->position.x, unit->position.y) <= 8) { unit->velocity.x = unit->velocity.y = 0; switch (unit->switchTarget->switchType) { case SWITCH_ENGINEER: unit->custom = 250; unit->action = &blobEngineerUseSwitch; unit->thinkTime = 1; break; case SWITCH_HACKER: unit->custom = 250; unit->action = &blobHackerUseSwitch; unit->thinkTime = 1; break; case SWITCH_DEMOLITION: //unit->custom = 500; unit->custom = 50; unit->action = &blobDemolitionUseSwitch1; unit->thinkTime = 1; break; default: handleSwitchUse(unit->switchTarget); unit->switchTarget->owner = NULL; unit->switchTarget = NULL; unit->completedOrders = true; unit->thinkTime = 50; break; } } } void blobDoOrderUse() { Unit *unit = (Unit*)self; if (unit->switchTarget != NULL) { blobUseAllocatedSwitch(); return; } self->thinkTime = Math::rrand(1, 3); self->velocity.x = 0; self->velocity.y = 0; Switch *closest = NULL; float distance; float lastDistance = 9999; Switch *swt; for (Entity *entity = (Entity*)entityManager->structureList.getFirstElement() ; entity != NULL ; entity = (Entity*)entity->next) { if (entity->entityType != ET_SWITCH) { continue; } swt = (Switch*)entity; if ((swt->owner != NULL) || (swt->requires != "")) { continue; } switch (swt->switchType) { case SWITCH_ENGINEER: if (self->definition->type != BLOB_ENGINEER) { continue; } break; case SWITCH_HACKER: if (self->definition->type != BLOB_HACKER) { continue; } break; case SWITCH_DEMOLITION: if (self->definition->type != BLOB_DEMOLITION) { continue; } break; default: if (swt->active) { continue; } break; } distance = Math::getDistance(entity->position, self->position); if (distance > 255) { continue; } if (distance < lastDistance) { closest = (Switch*)entity; lastDistance = distance; } } if (closest == NULL) { return; } unit->switchTarget = closest; unit->switchTarget->owner = unit; } void blobFollowOrders() { Unit *unit = (Unit*)self; switch (game->currentOrders) { case ORDERS_FOLLOW: unit->completedOrders = true; blobDoOrderFollow(); break; case ORDERS_RETREAT: unit->completedOrders = true; blobDoOrderRetreat(); break; case ORDERS_WAIT: unit->completedOrders = true; blobDoOrderWait(); break; case ORDERS_USE: if (!unit->completedOrders) { blobDoOrderUse(); } else { blobDoOrderWait(); } break; } } Unit *findTeeka() { for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if (unit->definition->type == BLOB_TEEKA) { return unit; } } #if DEV printf("WARNING: findTeeka() returning NULL!\n"); #endif return NULL; } void blobVanish() { debug(("blobVanish()\n")); self->custom = 1000; addTeleportParticles(self->position); self->flags |= EF_VANISHED; audio->playSound(SND_TELEPORT3, CH_SPAWN, camera->getSoundDistance(self->position)); self->action = NULL; } bool blobReappear() { int x = Math::rrand(-2, 2); int y = Math::rrand(-2, 2); if (x == 0) x = -1; if (y == 0) y = -1; x *= 12; y *= 12; self->position = player->position; self->position.x += x; self->position.y += y; self->position.z += 18; if (!unitCanSpawnInLocation(NULL, self->position)) { self->custom = 1; return false; } debug(("blobReappear()\n")); addTeleportParticles(self->position); self->flags &= ~EF_VANISHED; audio->playSound(SND_TELEPORT3, CH_SPAWN, camera->getSoundDistance(self->position)); self->action = blobFollowOrders; ((Unit*)self)->liquidLevel = 0; self->contentType = CONTENTS_SOLID; self->velocity.set(0, 0, 0); return true; } int getBlobSoldierCount() { int count = 0; for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if (unit->definition->type == BLOB_SOLDIER) { count++; } } return count; } void addBlob(Properties *props) { int type = entityManager->getEntityType(props); if ((game->changingLevel) && (type == BLOB_BOB)) { player->position = props->getVector("position"); return; } Unit *unit = entityManager->spawnBlob(type); unit->die = &generalBlobDie; unit->walk = &blobFollowOrders; unit->action = unit->walk; unit->draw = drawUnit; switch (unit->definition->type) { case BLOB_BOB: unit->name = "Bob"; player = unit; player->walk = NULL; player->action = NULL; debug(("Player assigned to Blob '%s', id %d\n", unit->getName(), unit->getUniqueId())); break; case BLOB_TEEKA: unit->name = "Teeka"; unit->currentWeapon = &entityManager->weaponDef[game->teekaWeapon]; break; case BLOB_MIA: unit->action = &miaBlobThink; unit->walk = &miaBlobThink; break; case BLOB_ASSIMILATING: unit->setName(randomNameGenerator->getRandomName()); unit->action = &assimiatedBlobThink; unit->walk = &assimiatedBlobThink; break; case BLOB_SOLDIER: switch (rand() % 3) { case 0: unit->currentWeapon = &entityManager->weaponDef[WEP_PISTOL]; break; case 1: unit->currentWeapon = &entityManager->weaponDef[WEP_UZI]; break; case 2: unit->currentWeapon = &entityManager->weaponDef[WEP_PLASMA_PISTOL]; break; } break; default: break; } unit->load(props); if (unit->name == "") { unit->setName(randomNameGenerator->getRandomName()); } debug(("addBlob() [%s %d %.2f %.2f]\n", unit->getName(), unit->definition->type, unit->health, unit->shield)); } void doBlobs() { bool helpless; float fallSpeed; for (Unit *unit = (Unit*)entityManager->blobList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { self = unit; bbManager->removeBox(unit); if ((game->cheatNPC) && (unit != player)) { unit->health = unit->maxHealth; } // Teeka cannot die on boss fights... if ((unit->definition->type == BLOB_TEEKA) && (entityManager->bossList.getSize() > 0)) { unit->health = unit->maxHealth; if (unit->totalCurrentDamage >= 50) { unit->totalCurrentDamage = 0; blobVanish(); } } if (unit != player) { if (unit->flags & EF_VANISHED) { if (player->health > 0) { unit->custom -= engine->getTimeDifference(TD_LOGIC); } Math::limit(&unit->custom, 0, 1000); if (unit->custom == 0) { if (!blobReappear()) { continue; } } else { continue; } } } if (unit->flags & EF_EXITTED) { if (!unit->referenced) { bbManager->destroyBox(unit); unit = (Unit*)unit->previous; entityManager->blobList.remove(unit->next); } continue; } if (unit->health <= 0) { if (unit->flags & EF_DEAD) { game->alterStat(_("Allies Killed"), 1); if ((unit == player) || (unit->definition->type == BLOB_TEEKA)) { game->setMissionStatus(MISSION_FAILED); unit->referenced = false; if (unit->definition->type == BLOB_TEEKA) { game->missionOverTimer = 0; } } else { unit = (Unit*)unit->previous; entityManager->blobList.remove(unit->next); } continue; } else if (!(unit->flags & EF_DYING)) { unit->die(); if (!unit->dead) { game->setInfoMessage(INFO_BAD, _("%s has been killed"), unit->getName()); fireTriggers(unit->getName(), TRIGGER_TYPE_ENTITY_KILLED); if (unit->definition->type == BLOB_MIA) { fireTriggers("ANY_MIA", TRIGGER_TYPE_ENTITY_KILLED); } unit->dead = true; } unit->flags |= EF_DYING; unit->flags |= EF_BOUNCES; } } // we can do this here because we've already checked it unit->referenced = false; helpless = (unit->helpless > 0); if ((!game->allStatic) && (game->cutsceneType != CUTSCENE_INGAME)) { if ((!helpless) || (unit->health < 1)) { if (unit->performNextAction(engine->getTimeDifference(TD_LOGIC))) { if (unit->action != NULL) { unit->action(); } } } } if (game->cutsceneType != CUTSCENE_INGAME) { unit->think(engine->getTimeDifference(TD_LOGIC)); } if (unit->flags & EF_TELEPORTING) { continue; } if ((unit->liquidLevel > 1) && (unit != player)) { unit->oxygen += (engine->getTimeDifference(TD_LOGIC) * 0.75); if ((unit != player) && (!(unit->flags & EF_VANISHED))) { if (unit->contentType & (CONTENTS_SLIME|CONTENTS_LAVA)) { blobVanish(); } } } if (withinViewableRange(unit)) { entityManager->addEntityToDrawList(unit); } // was helpless but now isn't - Stop sliding! if ((helpless) && (unit->helpless <= 0)) { if ((unit->flags & EF_ONGROUND) || (!(unit->contentType & CONTENTS_SOLID))) { unit->totalCurrentDamage = 0; unit->rotation.set(0, 0, 0); unit->flags &= ~EF_BOUNCES; if (unit == player) { unit->touch = NULL; } } else { unit->helpless = 1; } } unit->applyGravity(engine->getTimeDifference(TD_LOGIC)); if ((unit != player) && (unit->flags & EF_ONGROUND)) { keepUnitAwayFromEdges(unit); } fallSpeed = unit->realVelocity.z; if (fallSpeed <= FALL_DAMAGE_ACCEL) { unit->helpless = max(5, unit->helpless); unit->flags |= EF_BOUNCES; } moveEntity(unit); //unitContentChanged(unit); // take damage from falling far but not if you hit liquid if ((fallSpeed <= FALL_DAMAGE_ACCEL) && (unit->velocity.z > FALL_DAMAGE_ACCEL) && (unit->contentType & CONTENTS_SOLID)) { unit->velocity.z = 1.0; fallSpeed = fabs(fallSpeed * 1.5); unit->applyDamage(fallSpeed, DAMAGE_SPECIAL); unit->shield = -2.5; audio->playSound(SND_HIT, CH_ANY, camera->getSoundDistance(unit->position)); debug(("%s took %.2f damage from fall\n", unit->getName(), fallSpeed)); if (unit->flags & EF_FROZEN) { unit->mental = MAX_MENTAL; } if (unit == player) { game->alterStat(_("Total Damage Taken"), fallSpeed); } } if ((unit->position.z < bsp->minCords.z) && (unit->health > 0)) { printf("WARNING: Blob '%s' fell out of map at %s\n", unit->getName(), unit->position.toString()); unit->health = -100; unit->flags |= EF_DEAD; } if (unit->flags & EF_ONFIRE) { addOnFireParticles(); } bbManager->addBox(unit); } }