1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-22 03:51:58 +02:00

#14 centaur AI; fix #126

This commit is contained in:
XProger 2018-06-14 10:17:59 +03:00
parent 8b5c17445a
commit 4b22c474c8
7 changed files with 255 additions and 54 deletions

View File

@ -300,11 +300,6 @@ struct Character : Controller {
addBloodSpikes();
}
void addRicochet(const vec3 &pos, bool sound) {
game->addEntity(TR::Entity::RICOCHET, getRoomIndex(), pos);
if (sound)
game->playSound(TR::SND_RICOCHET, pos, Sound::PAN);
}
};
#endif

View File

@ -1318,6 +1318,12 @@ struct Controller {
mesh->renderModel(getEntity().modelIndex - 1, caustics);
}
}
void addRicochet(const vec3 &pos, bool sound) {
game->addEntity(TR::Entity::RICOCHET, getRoomIndex(), pos);
if (sound)
game->playSound(TR::SND_RICOCHET, pos, Sound::PAN);
}
};
Controller *Controller::first = NULL;

View File

@ -2,6 +2,11 @@
#define H_ENEMY
#include "character.h"
#include "trigger.h"
#define STALK_BOX (1024 * 3)
#define ESCAPE_BOX (1024 * 5)
#define ATTACK_BOX STALK_BOX
struct Enemy : Character {
@ -211,6 +216,23 @@ struct Enemy : Character {
return true;
}
bool targetIsVisible(float maxDist) {
if (targetInView && targetDist < maxDist && target->health > 0.0f) {
TR::Location from, to;
from.room = getRoomIndex();
from.pos = pos;
to.pos = target->pos;
// vertical offset to ~gun/head height
from.pos.y -= 768.0f;
if (target->stand != STAND_UNDERWATER && target->stand != STAND_ONWATER)
to.pos.y -= 768.0f;
return trace(from, to);
}
return false;
}
virtual void lookAt(Controller *target) {
Character::lookAt(targetInView ? target : NULL);
}
@ -246,16 +268,12 @@ struct Enemy : Character {
wound = true;
};
void bite(const vec3 &pos, float damage) {
void bite(int joint, const vec3 &offset, float damage) {
ASSERT(target);
target->hit(damage, this);
game->addEntity(TR::Entity::BLOOD, target->getRoomIndex(), pos);
game->addEntity(TR::Entity::BLOOD, target->getRoomIndex(), getJoint(joint) * offset);
}
#define STALK_BOX (1024 * 3)
#define ESCAPE_BOX (1024 * 5)
#define ATTACK_BOX STALK_BOX
Mood getMoodFixed() {
bool inZone = zone == target->zone;
@ -463,6 +481,19 @@ struct Enemy : Character {
return false;
}
void shot(TR::Entity::Type type, int joint, const vec3 &offset) {
vec3 from = getJoint(joint) * offset;
vec3 to = target->getBoundingBox().center();
Bullet *bullet = (Bullet*)game->addEntity(type, getRoomIndex(), from);
if (bullet) {
vec3 dir = to - from;
vec3 ang = vec3(-atan2f(dir.y, sqrtf(dir.x * dir.x + dir.z * dir.z)), atan2f(dir.x, dir.z), 0.0f);
ang += vec3(randf() * 2.0f - 1.0f, randf() * 2.0f - 1.0f, 0.0f) * (1.5f * DEG2RAD);
bullet->setAngle(ang);
}
}
};
#define WOLF_TURN_FAST (DEG2RAD * 150)
@ -586,7 +617,7 @@ struct Wolf : Enemy {
case STATE_ATTACK :
case STATE_BITE :
if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
bite(getJoint(jointHead).pos, state == STATE_ATTACK ? 50.0f : 100.0f);
bite(6, vec3(0.0f, -14.0f, 174.0f), state == STATE_ATTACK ? 50.0f : 100.0f);
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
}
return state == STATE_ATTACK ? STATE_RUN : state;
@ -713,7 +744,7 @@ struct Lion : Enemy {
case STATE_ATTACK :
case STATE_BITE :
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
bite(getJoint(jointHead).pos, state == STATE_ATTACK ? 150.0f : 250.0f);
bite(21, vec3(-2.0f, -10.0f, 132.0f), state == STATE_ATTACK ? 150.0f : 250.0f);
nextState = STATE_STOP;
}
}
@ -835,7 +866,7 @@ struct Gorilla : Enemy {
break;
case STATE_ATTACK :
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
bite(getJoint(jointChest).pos, 200.0f);
bite(15, vec3(0.0f, -19.0f, 75.0f), 200.0f);
nextState = STATE_STOP;
}
case STATE_LSTEP :
@ -1002,7 +1033,7 @@ struct Rat : Enemy {
case STATE_ATTACK :
case STATE_BITE :
if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
bite(getJoint(jointHead).pos, RAT_DAMAGE);
bite(3, vec3(0.0f, -11.0f, 108.0f), RAT_DAMAGE);
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_STOP;
}
break;
@ -1036,7 +1067,7 @@ struct Rat : Enemy {
case STATE_WATER_BITE :
if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
game->waterDrop(getJoint(jointHead).pos, 256.0f, 0.2f);
bite(getJoint(jointHead).pos, RAT_DAMAGE);
bite(3, vec3(0.0f, -11.0f, 108.0f), RAT_DAMAGE);
nextState = STATE_WATER_SWIM;
}
return STATE_NONE;
@ -1211,7 +1242,7 @@ struct Crocodile : Enemy {
return STATE_WALK;
case STATE_BITE :
if (nextState == STATE_NONE) {
bite(getJoint(jointHead).pos, CROCODILE_DAMAGE);
bite(9, vec3(5.0f, -21.0f, 467.0f), CROCODILE_DAMAGE);
nextState = STATE_STOP;
}
break;
@ -1243,7 +1274,7 @@ struct Crocodile : Enemy {
if (collide(target)) {
if (nextState != STATE_NONE)
return state;
bite(getJoint(jointHead).pos, CROCODILE_DAMAGE);
bite(9, vec3(5.0f, -21.0f, 467.0f), CROCODILE_DAMAGE);
nextState = STATE_WATER_SWIM;
}
return STATE_WATER_SWIM;
@ -1403,7 +1434,7 @@ struct Bear : Enemy {
case STATE_BITE :
case STATE_ATTACK :
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
bite(getJoint(jointHead).pos, state == STATE_BITE ? 200.0f : 400.0f);
bite(14, vec3(0.0f, 96.0f, 335.0f), state == STATE_BITE ? 200.0f : 400.0f);
nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
}
break;
@ -1484,7 +1515,7 @@ struct Bat : Enemy {
mood = MOOD_SLEEP;
return STATE_FLY;
} else
bite(getJoint(jointHead).pos, 2);
bite(4, vec3(0.0f, 16.0f, 45.0f), 2);
break;
case STATE_FLY :
if (collide(target)) {
@ -1723,7 +1754,7 @@ struct Raptor : Enemy {
case STATE_ATTACK_2 :
case STATE_BITE :
if (nextState == STATE_NONE && targetInView && (mask & HIT_MASK)) {
bite(getJoint(jointHead).pos, 100);
bite(22, vec3(0.0f, 66.0f, 318.0f), 100);
nextState = state == STATE_ATTACK_2 ? STATE_RUN : STATE_STOP;
}
break;
@ -1876,7 +1907,7 @@ struct Mutant : Enemy {
case STATE_ATTACK_3 :
if (nextState == STATE_NONE && (mask & HIT_MASK)) {
float damage = state == STATE_ATTACK_1 ? 150.0f : (state == STATE_ATTACK_2 ? 100.0f : 200.0f);
bite(getJoint(jointHead).pos, damage);
bite(10, vec3(-27.0f, 98.0f, 0.0f), damage);
nextState = STATE_STOP;
}
break;
@ -1899,6 +1930,7 @@ struct Mutant : Enemy {
};
struct GiantMutant : Enemy {
enum {
STATE_STOP = 1,
STATE_BORN = 8,
@ -1951,9 +1983,31 @@ struct GiantMutant : Enemy {
}
};
#define CENTAUR_TURN_FAST (DEG2RAD * 120)
#define CENTAUR_DIST_RUN (1024 + 512)
#define CENTAUR_DIST_SHOT (1024 * 1024)
struct Centaur : Enemy {
Centaur(IGame *game, int entity) : Enemy(game, entity, 20, 341, 400.0f, 0.5f) {
enum {
HIT_MASK = 0x030199,
};
enum {
ANIM_DEATH = 8,
};
enum {
STATE_NONE,
STATE_STOP,
STATE_FIRE,
STATE_RUN,
STATE_AIM,
STATE_DEATH,
STATE_IDLE,
};
Centaur(IGame *game, int entity) : Enemy(game, entity, 120, 341, 400.0f, 1.0f) {
jointChest = 10;
jointHead = 17;
}
@ -1961,14 +2015,94 @@ struct Centaur : Enemy {
virtual int getStateGround() {
if (!think(true))
return state;
if (nextState == state)
nextState = STATE_NONE;
int mask = collide(target);
switch (state) {
case STATE_STOP :
if (nextState != STATE_NONE)
return nextState;
if (targetCanAttack && targetDist < CENTAUR_DIST_RUN)
return STATE_RUN;
if (targetIsVisible(CENTAUR_DIST_SHOT))
return STATE_AIM;
return STATE_RUN;
case STATE_RUN :
if (targetCanAttack && targetDist < CENTAUR_DIST_RUN) {
nextState = STATE_IDLE;
return STATE_STOP;
}
if (targetIsVisible(CENTAUR_DIST_SHOT)) {
nextState = STATE_AIM;
return STATE_STOP;
}
if (rand() < 96) {
nextState = STATE_IDLE;
return STATE_STOP;
}
break;
case STATE_AIM :
if (nextState != STATE_NONE)
return nextState;
if (targetIsVisible(CENTAUR_DIST_SHOT))
return STATE_FIRE;
return STATE_STOP;
case STATE_FIRE :
if (nextState != STATE_NONE)
break;
nextState = STATE_AIM;
shot(TR::Entity::CENTAUR_BULLET, 13, vec3(11.0f, 415.0f, 41.0f));
break;
case STATE_IDLE :
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
bite(5, vec3(50.0f, 30.0f, 0.0f), 200);
nextState = STATE_STOP;
}
break;
}
return state;
}
virtual int getStateDeath() {
if (state == STATE_DEATH) return state;
return animation.setAnim(ANIM_DEATH);
}
virtual void updatePosition() {
float angleY = 0.0f;
getTargetInfo(0, NULL, NULL, &angleY, NULL);
if (state == STATE_RUN)
turn(angleY, CENTAUR_TURN_FAST);
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target);
}
virtual void deactivate(bool removeFromList = false) {
if (!removeFromList) {
if (!explodeMask)
explode(0xffffffff);
return;
}
Enemy::deactivate(removeFromList);
}
virtual void update() {
bool exploded = explodeMask != 0;
Enemy::update();
if (exploded && !explodeMask) {
deactivate(true);
flags.invisible = true;
}
}
};
@ -2123,8 +2257,8 @@ struct Human : Enemy {
STATE_FIRE
};
int jointGun;
int animDeath;
int jointGun;
int animDeath;
Human(IGame *game, int entity, float health) : Enemy(game, entity, health, 100, 375.0f, 1.0f), animDeath(-1) {
jointGun = 0;
@ -2166,28 +2300,11 @@ struct Human : Enemy {
virtual void onDead() {}
bool targetIsVisible() {
if (targetInView && targetDist < HUMAN_DIST_SHOT && target->health > 0.0f) {
TR::Location from, to;
from.room = getRoomIndex();
from.pos = pos;
to.pos = target->pos;
// vertical offset to ~gun/head height
from.pos.y -= 768.0f;
if (target->stand != STAND_UNDERWATER && target->stand != STAND_ONWATER)
to.pos.y -= 768.0f;
return trace(from, to);
}
return false;
}
bool doShot(float damage, const vec3 &muzzleOffset) {
game->addMuzzleFlash(this, jointGun, muzzleOffset, -1);
if (targetDist < HUMAN_DIST_SHOT && randf() < ((HUMAN_DIST_SHOT - targetDist) / HUMAN_DIST_SHOT - 0.25f)) {
bite(target->getJoint(rand() % target->getModel()->mCount).pos, damage);
bite(rand() % target->getModel()->mCount, vec3(0.0f), damage);
game->playSound(target->stand == STAND_UNDERWATER ? TR::SND_HIT_UNDERWATER : TR::SND_HIT, target->pos, Sound::PAN);
return true;
}
@ -2244,7 +2361,7 @@ struct Larson : Human {
nextState = STATE_WAIT;
else if (mood == MOOD_ESCAPE)
nextState = STATE_RUN;
else if (targetIsVisible())
else if (targetIsVisible(HUMAN_DIST_SHOT))
nextState = STATE_AIM;
else if (!targetInView || targetDist > HUMAN_DIST_WALK)
nextState = STATE_RUN;
@ -2254,7 +2371,7 @@ struct Larson : Human {
case STATE_RUN :
if (mood == MOOD_SLEEP && randf() < HUMAN_WAIT)
nextState = STATE_WAIT;
else if (targetIsVisible())
else if (targetIsVisible(HUMAN_DIST_SHOT))
nextState = STATE_AIM;
else if (targetInView && targetDist < HUMAN_DIST_WALK)
nextState = STATE_WALK;
@ -2264,7 +2381,7 @@ struct Larson : Human {
case STATE_AIM :
if (nextState != STATE_NONE)
return nextState;
if (targetIsVisible())
if (targetIsVisible(HUMAN_DIST_SHOT))
return STATE_FIRE;
return STATE_STOP;
case STATE_FIRE :

View File

@ -191,7 +191,7 @@
E( WATERFALL ) \
E( NATLA_BULLET ) \
E( MUTANT_BULLET ) \
E( MUTANT_GRENADE ) \
E( CENTAUR_BULLET ) \
E( UNUSED_16 ) \
E( UNUSED_17 ) \
E( LAVA_PARTICLE ) \
@ -1790,7 +1790,7 @@ namespace TR {
}
bool isDoor() const {
return type >= DOOR_1 && type <= DOOR_6;
return type >= DOOR_1 && type <= DOOR_8;
}
bool isCollider() const {

View File

@ -476,7 +476,6 @@ struct Lara : Character {
if (level->extra.braid > -1)
braid = new Braid(this, (level->version & (TR::VER_TR2 | TR::VER_TR3)) ? vec3(-2.0f, -16.0f, -48.0f) : vec3(-4.0f, 24.0f, -48.0f));
#ifdef _DEBUG
//reset(14, vec3(40448, 3584, 60928), PI * 0.5f, STAND_ONWATER); // gym (pool)
//reset(0, vec3(74858, 3072, 20795), 0); // level 1 (dart)
//reset(14, vec3(20215, 6656, 52942), PI); // level 1 (bridge)
@ -522,6 +521,7 @@ struct Lara : Character {
//reset(44, vec3(75803, -11008, 21097), 90 * DEG2RAD); // Level 10a (boat)
//reset(47, vec3(50546, -13056, 53783), 270 * DEG2RAD); // Level 10b (trap door slope)
//reset(59, vec3(42907, -13056, 63012), 270 * DEG2RAD); // Level 10b (doppelganger)
//reset(53, vec3(39617, -18385, 48950), 180 * DEG2RAD); // Level 10b (centaur)
//reset(50, vec3(52122, -18688, 47313), 150 * DEG2RAD); // Level 10b (scion holder pickup)
//reset(50, vec3(53703, -18688, 13769), PI); // Level 10c (scion holder)
//reset(19, vec3(35364, -512, 40199), PI * 0.5f); // Level 10c (lava flow)
@ -529,7 +529,6 @@ struct Lara : Character {
//reset(21, vec3(47668, -10752, 32163), 0); // Level 10c (lava emitter)
//reset(10, vec3(90443, 11264 - 256, 114614), PI, STAND_ONWATER); // villa mortal 2
//dbgBoxes = NULL;
#endif
if (!level->isCutsceneLevel()) {
if (getRoom().flags.water) {

View File

@ -859,6 +859,9 @@ struct Level : IGame {
case TR::Entity::MIDAS_HAND : return new MidasHand(this, index);
case TR::Entity::SCION_TARGET : return new ScionTarget(this, index);
case TR::Entity::WATERFALL : return new Waterfall(this, index);
case TR::Entity::NATLA_BULLET :
case TR::Entity::MUTANT_BULLET :
case TR::Entity::CENTAUR_BULLET : return new Bullet(this, index);
case TR::Entity::TRAP_LAVA : return new TrapLava(this, index);
case TR::Entity::BUBBLE : return new Bubble(this, index);
case TR::Entity::EXPLOSION : return new Explosion(this, index);
@ -1471,10 +1474,19 @@ struct Level : IGame {
if (isModel) { // model
vec3 pos = controller->getPos();
if (ambientCache) {
AmbientCache::Cube cube;
ambientCache->getAmbient(roomIndex, pos, cube);
if (cube.status == AmbientCache::Cube::READY)
memcpy(controller->ambient, cube.colors, sizeof(cube.colors)); // store last calculated ambient into controller
if (!controller->getEntity().isDoor()) { // no advanced ambient lighting for secret (all) doors
AmbientCache::Cube cube;
ambientCache->getAmbient(roomIndex, pos, cube);
if (cube.status == AmbientCache::Cube::READY)
memcpy(controller->ambient, cube.colors, sizeof(cube.colors)); // store last calculated ambient into controller
} else {
controller->ambient[0] =
controller->ambient[1] =
controller->ambient[2] =
controller->ambient[3] =
controller->ambient[4] =
controller->ambient[5] = vec4(Core::active.material.y);
}
Core::active.shader->setParam(uAmbient, controller->ambient[0], 6);
}
}

View File

@ -1547,4 +1547,76 @@ struct StoneItem : Controller {
}
};
#define CENTAUR_BULLET_DIST SQR(1024.0f)
#define CENTAUR_BULLET_DAMAGE 100.0f
struct Bullet : Controller {
vec3 velocity;
Bullet(IGame *game, int entity) : Controller(game, entity) {
velocity = vec3(200.0f) * 30.0f;
intensity = 4096;
activate();
}
void setAngle(const vec3 &ang) {
angle = ang;
float speed;
switch (getEntity().type) {
case TR::Entity::NATLA_BULLET : speed = 220.0f; break;
case TR::Entity::MUTANT_BULLET : speed = 250.0f; break;
case TR::Entity::CENTAUR_BULLET : speed = 220.0f; break;
default : speed = 200.0f;
}
velocity = getDir() * (speed * 30.0f);
}
virtual void update() {
//getRoom().removeDynLight(entity);
pos = pos + velocity * Core::deltaTime;
game->getLevel()->getSector(roomIndex, pos);
Controller::update();
//getRoom().addDynLight(entity, pos, vec4(1, 1, 0, 1.0f / 1024.0f));
bool directHit = false;
Character *lara = (Character*)game->getLara(pos);
if (!collide(lara)) {
TR::Level::FloorInfo info;
getFloorInfo(getRoomIndex(), pos, info);
if (!(pos.y > info.floor || pos.y < info.ceiling || !insideRoom(pos, getRoomIndex())))
return;
} else
directHit = true;
//getRoom().removeDynLight(entity);
switch (getEntity().type) {
case TR::Entity::CENTAUR_BULLET :
if (directHit) {
lara->hit(CENTAUR_BULLET_DAMAGE);
game->playSound(lara->stand == Character::STAND_UNDERWATER ? TR::SND_HIT_UNDERWATER : TR::SND_HIT, lara->pos, Sound::PAN);
} else {
for (int i = 0; i < 2; i++) {
Controller *lara = game->getLara(i);
if (!lara) continue;
float dist = (pos - lara->pos).length2();
if (dist < CENTAUR_BULLET_DIST)
lara->hit(CENTAUR_BULLET_DAMAGE * (CENTAUR_BULLET_DIST - dist) / CENTAUR_BULLET_DIST);
}
}
game->addEntity(TR::Entity::EXPLOSION, getRoomIndex(), pos);
break;
case TR::Entity::MUTANT_BULLET :
game->getLara()->addRicochet(pos, true);
break;
default : ;
}
game->removeEntity(this);
}
};
#endif