1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-09 14:47:02 +02:00

#14 Basic logic for Wolf, Bear and Bat; simple path-finding; Laras hit animation (push)

This commit is contained in:
XProger
2017-05-10 01:39:28 +03:00
parent 741e9a90ac
commit c4ab64dec6
8 changed files with 1025 additions and 366 deletions

View File

@@ -741,46 +741,144 @@ struct WaterCache {
#undef DETAIL
};
/*
struct LightCache {
struct ZoneCache {
struct Item {
int room;
int index;
float intensity;
} items[MAX_CACHED_LIGHTS];
uint16 zone;
uint16 count;
uint16 *zones;
uint16 *boxes;
Item *next;
void updateLightCache(const TR::Level &level, const vec3 &pos, int room) {
// update intensity
for (int i = 0; i < MAX_CACHED_LIGHTS; i++) {
Item &item = items[i];
if (c.intensity < 0.0f) continue;
TR::Room::Light &light = level.rooms[c.room].lights[i];
c.intensity = max(0.0f, 1.0f - (pos - vec3(float(light.x), float(light.y), float(light.z))).length2() / ((float)light.radius * (float)light.radius));
Item(uint16 zone, uint16 count, uint16 *zones, uint16 *boxes, Item *next) :
zone(zone), count(count), zones(zones), boxes(boxes), next(next) {}
~Item() {
delete[] boxes;
delete next;
}
} *items;
IGame *game;
// dummy arrays for path search
uint16 *nodes;
uint16 *parents;
uint16 *weights;
ZoneCache(IGame *game) : items(NULL), game(game) {
TR::Level *level = game->getLevel();
nodes = new uint16[level->boxesCount * 3];
parents = nodes + level->boxesCount;
weights = nodes + level->boxesCount * 2;
}
// check for new lights
int index = getLightIndex(pos, room);
if (index >= 0 && (items[0].room != room || items[0].index != index)) [
TR::Room::Light &light = level.rooms[room].lights[index];
float intensity = max(0.0f, 1.0f - (lara->pos - vec3(float(light.x), float(light.y), float(light.z))).length2() / ((float)light.radius * (float)light.radius));
int i = 0;
while (i < MAX_CACHED_LIGHTS && lightCache[i].intensity > intensity) // get sorted place
i++;
if (i < MAX_CACHED_LIGHTS) { // insert light
for (int j = MAX_CACHED_LIGHTS - 1; j > i; j--)
lightCache[j] = lightCache[j - 1];
lightCache[i].room = room;
lightCache[i].index = index;
lightCache[i].intensity = intensity;
~ZoneCache() {
delete items;
delete[] nodes;
}
Item *getBoxes(uint16 zone, uint16 *zones) {
Item *item = items;
while (item) {
if (item->zone == zone && item->zones == zones)
return item;
item = item->next;
}
int count = 0;
TR::Level *level = game->getLevel();
for (int i = 0; i < level->boxesCount; i++)
if (zones[i] == zone)
nodes[count++] = i;
ASSERT(count > 0);
uint16 *boxes = new uint16[count];
memcpy(boxes, nodes, sizeof(uint16) * count);
return items = new Item(zone, count, zones, boxes, items);
}
uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
if (boxStart == 0xFFFF || boxEnd == 0xFFFF)
return 0;
TR::Level *level = game->getLevel();
memset(parents, 0xFF, sizeof(uint16) * level->boxesCount); // fill parents by 0xFFFF
memset(weights, 0x00, sizeof(uint16) * level->boxesCount); // zeroes weights
uint16 count = 0;
nodes[count++] = boxEnd;
uint16 zone = zones[boxStart];
if (zone != zones[boxEnd])
return 0;
TR::Box &b = level->boxes[boxStart];
int sx = (b.minX + b.maxX) >> 11; // box center / 1024
int sz = (b.minZ + b.maxZ) >> 11;
while (count) {
// get min weight
int minI = 0;
int minW = weights[nodes[minI]];
for (int i = 1; i < count; i++)
if (weights[nodes[i]] < minW) {
minI = i;
minW = weights[nodes[i]];
}
int cur = nodes[minI];
// peek min weight item from array
count--;
for (int i = minI; i < count; i++)
nodes[i] = nodes[i + 1];
// check for end of path
if (cur == boxStart) {
count = 0;
while (cur != boxEnd) {
nodes[count++] = cur;
cur = parents[cur];
}
nodes[count++] = cur;
*boxes = nodes;
return count;
}
// add overlap boxes
TR::Box &b = game->getLevel()->boxes[cur];
TR::Overlap *overlap = &level->overlaps[b.overlap.index];
do {
uint16 index = overlap->boxIndex;
// unvisited yet
if (parents[index] != 0xFFFF)
continue;
// has same zone
if (zones[index] != zone)
continue;
// check for height difference
int d = level->boxes[index].floor - b.floor;
if (d > ascend || d < descend)
continue;
int dx = sx - ((b.minX + b.maxX) >> 11);
int dz = sz - ((b.minZ + b.maxZ) >> 11);
int w = abs(dx) + abs(dz);
ASSERT(count < level->boxesCount);
nodes[count++] = index;
parents[index] = cur;
weights[index] = weights[cur] + w;
} while (!(overlap++)->end);
}
return 0;
}
};
*/
#undef UNDERWATER_COLOR

View File

@@ -46,11 +46,18 @@ struct Character : Controller {
updateZone();
}
void updateZone() {
TR::Level *level = game->getLevel();
bool updateZone() {
int dx, dz;
box = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz).boxIndex;
zone = flying ? level->zones[0].fly[box] : level->zones[0].ground1[box];
TR::Room::Sector &s = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz);
if (s.boxIndex == 0xFFFF)
return false;
box = s.boxIndex;
zone = getZones()[box];
return true;
}
uint16* getZones() {
return flying ? level->zones[0].fly : level->zones[0].ground1;
}
void rotateY(float delta) {
@@ -130,6 +137,11 @@ struct Character : Controller {
animation.setState(getStateDefault());
}
virtual void updateTilt(float value, float tiltSpeed, float tiltMax) {
value = clamp(value, -tiltMax, +tiltMax);
decrease(value - angle.z, angle.z, tiltSpeed);
}
virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) {
// calculate turning tilt
if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) {
@@ -154,11 +166,16 @@ struct Character : Controller {
stand = getStand();
updateState();
Controller::update();
if (getEntity().flags.active) {
updateVelocity();
updatePosition();
if (p != pos) {
if (updateZone())
updateLights();
updateZone();
else
pos = p;
}
}
}

View File

@@ -8,7 +8,6 @@
#include "collision.h"
#define GRAVITY (6.0f * 30.0f)
#define NO_OVERLAP 0x7FFFFFFF
#define SPRITE_FPS 10.0f
#define MAX_LAYERS 4
@@ -20,6 +19,8 @@ struct IGame {
virtual TR::Level* getLevel() { return NULL; }
virtual MeshBuilder* getMesh() { return NULL; }
virtual Controller* getCamera() { return NULL; }
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { return 0; }
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; }
virtual void setClipParams(float clipSign, float clipHeight) {}
virtual void setWaterParams(float height) {}
virtual void updateParams() {}
@@ -178,38 +179,6 @@ struct Controller {
return pos;
}
int getOverlap(int fromX, int fromY, int fromZ, int toX, int toZ) const {
int dx, dz;
TR::Room::Sector &s = level->getSector(getEntity().room, fromX, fromZ, dx, dz);
if (s.boxIndex == 0xFFFF)
return NO_OVERLAP;
TR::Box &b = level->boxes[s.boxIndex];
if (b.contains(toX, toZ))
return 0;
int floor = NO_OVERLAP;
int delta = NO_OVERLAP;
TR::Overlap *o = &level->overlaps[b.overlap.index];
do {
TR::Box &ob = level->boxes[o->boxIndex];
if (ob.contains(toX, toZ)) { // get min delta
int d = abs(b.floor - ob.floor);
if (d < delta) {
floor = ob.floor;
delta = d;
}
}
} while (!(o++)->end);
if (floor == NO_OVERLAP)
return NO_OVERLAP;
return b.floor - floor;
}
Sound::Sample* playSound(int id, const vec3 &pos, int flags) const {
if (level->version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET)
return NULL;
@@ -276,12 +245,50 @@ struct Controller {
return true;
}
virtual Box getBoundingBox() {
Box getBoundingBox() {
return getBoundingBoxLocal() * getMatrix();
}
virtual Box getBoundingBoxLocal() {
return animation.getBoundingBox(vec3(0, 0, 0), 0);
Box getBoundingBoxLocal(bool oriented = false) {
return animation.getBoundingBox(vec3(0, 0, 0), oriented ? getEntity().rotation.value / 0x4000 : 0);
}
void getSpheres(Sphere *spheres) {
TR::Model *m = getModel();
Basis basis(getMatrix());
for (int i = 0; i < m->mCount; i++) {
TR::Mesh &aMesh = level->meshes[level->meshOffsets[m->mStart + i]];
vec3 center = animation.getJoints(basis, i, true) * aMesh.center;
spheres[i] = Sphere(center, aMesh.radius);
}
}
bool collide(Controller *controller) {
TR::Model *a = getModel();
TR::Model *b = getModel();
if (!a || !b)
return false;
if (!getBoundingBox().intersect(controller->getBoundingBox()))
return false;
ASSERT(a->mCount <= 34);
ASSERT(b->mCount <= 34);
Sphere aSpheres[34];
Sphere bSpheres[34];
getSpheres(aSpheres);
controller->getSpheres(bSpheres);
for (int i = 0; i < a->mCount; i++)
for (int j = 0; j < b->mCount; j++)
if (aSpheres[i].intersect(bSpheres[j]))
return true;
return false;
}
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham

View File

@@ -288,8 +288,8 @@ namespace Debug {
for (int x = 0; x < r.xSectors; x++) {
TR::Room::Sector &s = r.sectors[x * r.zSectors + z];
if (s.boxIndex != 0xFFFF) {
bool blockable = level.boxes[s.boxIndex].overlap.value & 0x8000;
bool block = level.boxes[s.boxIndex].overlap.value & 0x4000;
bool blockable = level.boxes[s.boxIndex].overlap.blockable;
bool block = level.boxes[s.boxIndex].overlap.block;
int floor = level.boxes[s.boxIndex].floor;
if (blockable || block) {
@@ -304,10 +304,22 @@ namespace Debug {
}
void debugOverlaps(const TR::Level &level, int boxIndex) {
glColor4f(1.0f, 1.0f, 0.0f, 0.25f);
char str[64];
TR::Box &b = level.boxes[boxIndex];
sprintf(str, "%d", boxIndex);
Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 1, 0, 1), str);
glColor4f(0.0f, 1.0f, 0.0f, 0.25f);
Core::setBlending(bmAlpha);
debugBox(b);
TR::Overlap *o = &level.overlaps[level.boxes[boxIndex].overlap.index];
do {
TR::Box &b = level.boxes[o->boxIndex];
sprintf(str, "%d", o->boxIndex);
Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 0, 1, 1), str);
glColor4f(0.0f, 0.0f, 1.0f, 0.25f);
Core::setBlending(bmAlpha);
debugBox(b);
} while (!(o++)->end);
}
@@ -369,7 +381,7 @@ namespace Debug {
for (int i = 0; i < level.entitiesCount; i++) {
TR::Entity &e = level.entities[i];
sprintf(buf, "%d", (int)e.type);
sprintf(buf, "%d (%d)", (int)e.type, i);
Debug::Draw::text(vec3(e.x, e.y, e.z), e.flags.active ? vec4(0, 0, 0.8, 1) : vec4(0.8, 0, 0, 1), buf);
}
@@ -381,6 +393,24 @@ namespace Debug {
}
}
void path(TR::Level &level, Enemy *enemy) {
Enemy::Path *path = enemy->path;
if (!path || !enemy->target) return;
for (int i = 0; i < path->count; i++) {
TR::Box &b = level.boxes[path->boxes[i]];
if (i == path->index)
glColor4f(0.5, 0.5, 0.0, 0.5);
else
glColor4f(0.0, 0.5, 0.0, 0.5);
debugBox(b);
}
Core::setDepthTest(false);
Draw::point(enemy->waypoint, vec4(1.0));
Core::setDepthTest(true);
}
void zones(const TR::Level &level, Lara *lara) {
Core::setDepthTest(false);
for (int i = 0; i < level.roomsCount; i++)
@@ -446,12 +476,13 @@ namespace Debug {
if (!level.meshOffsets[sm->mesh]) continue;
const TR::Mesh &mesh = level.meshes[level.meshOffsets[sm->mesh]];
/*
{
char buf[255];
sprintf(buf, "flags %d", (int)mesh.flags.value);
Debug::Draw::text(offset + (box.min + box.max) * 0.5f, vec4(0.5, 0.5, 1.0, 1), buf);
}
*/
}
}
@@ -462,76 +493,29 @@ namespace Debug {
if (!controller) continue;
mat4 matrix = controller->getMatrix();
Box box = controller->animation.getBoundingBox(vec3(0.0f), 0);
Basis basis(matrix);
TR::Model *m = controller->getModel();
if (!m) continue;
Box box = controller->getBoundingBoxLocal();
Debug::Draw::box(matrix, box.min, box.max, vec4(1.0));
Sphere spheres[34];
ASSERT(m->mCount <= 34);
controller->getSpheres(spheres);
for (int j = 0; j < level.modelsCount; j++) {
TR::Model &m = level.models[j];
TR::Node *node = m.node < level.nodesDataSize ? (TR::Node*)&level.nodesData[m.node] : NULL;
if (!node) continue; // ???
if (e.type == m.type) {
ASSERT(m.animation < 0xFFFF);
int fSize = sizeof(TR::AnimFrame) + m.mCount * sizeof(uint16) * 2;
TR::Animation *anim = controller->animation;
TR::AnimFrame *frame = (TR::AnimFrame*)&level.frameData[(anim->frameOffset + (controller ? int((controller->animation.time * 30.0f / anim->frameRate)) * fSize : 0) >> 1)];
//mat4 m;
//m.identity();
//m.translate(vec3(frame->x, frame->y, frame->z).lerp(vec3(frameB->x, frameB->y, frameB->z), k));
int sIndex = 0;
mat4 stack[20];
mat4 joint;
joint.identity();
if (frame) joint.translate(frame->pos);
for (int k = 0; k < m.mCount; k++) {
if (k > 0 && node) {
TR::Node &t = node[k - 1];
if (t.flags & 0x01) joint = stack[--sIndex];
if (t.flags & 0x02) stack[sIndex++] = joint;
ASSERT(sIndex >= 0 && sIndex < 20);
joint.translate(vec3(t.x, t.y, t.z));
}
vec3 a = frame ? frame->getAngle(k) : vec3(0.0f);
mat4 rot;
rot.identity();
rot.rotateY(a.y);
rot.rotateX(a.x);
rot.rotateZ(a.z);
joint = joint * rot;
int offset = level.meshOffsets[m.mStart + k];
TR::Mesh *mesh = (TR::Mesh*)&level.meshes[offset];
//if (!mesh->flags) continue;
Debug::Draw::sphere(matrix * joint * mesh->center, mesh->radius, vec4(0, 1, 1, 0.5f));
for (int joint = 0; joint < m->mCount; joint++) {
Sphere &sphere = spheres[joint];
Debug::Draw::sphere(sphere.center, sphere.radius, vec4(0, 1, 1, 0.5f));
/*
{ //if (e.id != 0) {
char buf[255];
sprintf(buf, "(%d) radius %d flags %d", (int)e.type, (int)mesh->radius, (int)mesh->flags.value);
Debug::Draw::text(matrix * joint * mesh->center, vec4(0.5, 1, 0.5, 1), buf);
Debug::Draw::text(sphere.center, vec4(0.5, 1, 0.5, 1), buf);
}
*/
}
Debug::Draw::box(matrix, frame->box.min(), frame->box.max(), vec4(1.0));
break;
}
}
}
}

View File

@@ -4,9 +4,77 @@
#include "character.h"
struct Enemy : Character {
bool bitten;
Enemy(IGame *game, int entity, int health) : Character(game, entity, health), bitten(false) {}
struct Path {
int16 index;
int16 count;
uint16 *boxes;
TR::Level *level;
Path(TR::Level *level, uint16 *boxes, int count) : index(0), count(count), boxes(new uint16[count]), level(level) {
memcpy(this->boxes, boxes, count * sizeof(boxes[0]));
}
~Path() {
delete[] boxes;
}
bool getNextPoint(TR::Level *level, vec3 &point) {
if (index >= count - 1)
return false;
TR::Box &a = level->boxes[boxes[index++]];
TR::Box &b = level->boxes[boxes[index]];
int minX = max(a.minX, b.minX);
int minZ = max(a.minZ, b.minZ);
int maxX = min(a.maxX, b.maxX);
int maxZ = min(a.maxZ, b.maxZ);
point.x = float(minX + 512) + randf() * (maxX - minX - 1024);
point.y = float((a.floor + b.floor) / 2);
point.z = float(minZ + 512) + randf() * (maxZ - minZ - 1024);
return true;
}
};
enum AI {
AI_FIXED, AI_RANDOM
} ai;
enum Mood {
MOOD_SLEEP, MOOD_STALK, MOOD_ATTACK, MOOD_ESCAPE
} mood;
bool wound;
int nextState;
int targetBox;
vec3 waypoint;
float thinkTime;
float aggression;
int radius;
int stepHeight;
int dropHeight;
Character *target;
Path *path;
int jointChest;
int jointHead;
Enemy(IGame *game, int entity, int health, int radius, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(0.0f), aggression(aggression), radius(radius), target(NULL), path(NULL) {
stepHeight = 256;
dropHeight = -256;
jointChest = jointHead = -1;
}
virtual ~Enemy() {
delete path;
}
virtual bool activate(ActionCommand *cmd) {
#ifdef LEVEL_EDITOR
@@ -20,9 +88,10 @@ struct Enemy : Character {
for (int i = 0; i < level->entitiesCount; i++)
if (level->entities[i].type == TR::Entity::LARA) {
target = i;
target = (Character*)level->entities[i].controller;
break;
}
ASSERT(target);
return true;
}
@@ -31,22 +100,71 @@ struct Enemy : Character {
velocity = getDir() * animation.getSpeed();
}
virtual void updatePosition() {
if (!getEntity().flags.active) return;
vec3 p = pos;
pos += velocity * Core::deltaTime * 30.0f;
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
if (pos.y - info.floor > 1024) {
pos = p;
return;
bool checkPoint(int x, int z) {
TR::Box &a = level->boxes[box];
if (a.contains(x, z))
return true;
TR::Overlap *o = &level->overlaps[a.overlap.index];
do {
TR::Box &b = level->boxes[o->boxIndex];
if (!b.contains(x, z))
continue;
if (getZones()[o->boxIndex] == zone) {
int d = a.floor - b.floor;
if (d <= stepHeight && d >= dropHeight)
return true;
}
} while (!(o++)->end);
return false;
}
void clipByBox(vec3 &pos) {
int px = int(pos.x);
int pz = int(pos.z);
int nx = px;
int nz = pz;
TR::Box &a = level->boxes[box];
if (!checkPoint(px - radius, pz)) nx = a.minX + radius;
if (!checkPoint(px + radius, pz)) nx = a.maxX - radius;
if (!checkPoint(px, pz - radius)) nz = a.minZ + radius;
if (!checkPoint(px, pz + radius)) nz = a.maxZ - radius;
if (px != nx) pos.x = float(nx);
if (pz != nz) pos.z = float(nz);
}
virtual void updatePosition() {
if (!getEntity().flags.active) return;
vec3 p = pos;
pos += velocity * Core::deltaTime * 30.0f;
clipByBox(pos);
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
int dx, dz;
TR::Room::Sector &s = level->getSector(info.roomNext != TR::NO_ROOM ? info.roomNext : getRoomIndex(), int(pos.x), int(pos.z), dx, dz);
if (s.boxIndex != 0xFFFF && zone == getZones()[s.boxIndex]) {
switch (stand) {
case STAND_GROUND : pos.y = float(info.floor); break;
case STAND_AIR : pos.y = clamp(pos.y, float(info.ceiling), float(info.floor)); break;
case STAND_GROUND : {
float fallSpeed = 2048.0f * Core::deltaTime;
decrease(info.floor - pos.y, pos.y, fallSpeed);
break;
}
case STAND_AIR :
pos.y = clamp(pos.y, float(info.ceiling), float(info.floor));
break;
default : ;
}
} else
pos = p;
updateEntity();
checkRoom();
@@ -88,11 +206,7 @@ struct Enemy : Character {
}
bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
if (target == -1) return false;
Character *character = (Character*)level->entities[target].controller;
if (character->health <= 0) return false;
vec3 p = character->pos;
vec3 p = waypoint;
p.y -= height;
if (pos) *pos = p;
vec3 a = p - this->pos;
@@ -109,12 +223,17 @@ struct Enemy : Character {
}
int turn(float delta, float speed) {
speed *= Core::deltaTime;
decrease(delta, angle.y, speed);
float w = speed * Core::deltaTime;
updateTilt(delta, w, speed * 0.1f);
if (delta != 0.0f) {
decrease(delta, angle.y, w);
if (speed != 0.0f) {
velocity = velocity.rotateY(-speed);
velocity = velocity.rotateY(-w);
return speed < 0 ? LEFT : RIGHT;
}
}
return 0;
}
@@ -128,21 +247,230 @@ struct Enemy : Character {
return 0;
}
virtual void hit(int damage, Controller *enemy = NULL) {
Character::hit(damage, enemy);
wound = true;
};
void bite(const vec3 &pos, int damage) {
if (bitten) return;
bitten = true;
ASSERT(target > -1);
Character *c = (Character*)level->entities[target].controller;
c->hit(damage, this);
Sprite::add(game, TR::Entity::BLOOD, c->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
ASSERT(target);
target->hit(damage, this);
Sprite::add(game, TR::Entity::BLOOD, target->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
}
#define STALK_BOX (1024 * 3)
#define ESCAPE_BOX (1024 * 5)
#define ATTACK_BOX STALK_BOX
Mood getMoodFixed() {
bool inZone = zone == target->zone;
if (mood == MOOD_SLEEP || mood == MOOD_STALK)
return inZone ? MOOD_ATTACK : (wound ? MOOD_ESCAPE : mood);
if (mood == MOOD_ATTACK)
return inZone ? mood : MOOD_SLEEP;
return inZone ? MOOD_ATTACK : mood;
}
Mood getMoodRandom() {
bool inZone = zone == target->zone;
bool brave = rand() < (mood != MOOD_ESCAPE ? 0x7800 : 0x0100) && inZone;
if (mood == MOOD_SLEEP || mood == MOOD_STALK) {
if (wound && !brave)
return MOOD_ESCAPE;
if (inZone) {
int dx = abs(int(pos.x - target->pos.x));
int dz = abs(int(pos.z - target->pos.z));
return ((dx <= ATTACK_BOX && dz <= ATTACK_BOX) || (mood == MOOD_STALK && targetBox == -1)) ? MOOD_ATTACK : MOOD_STALK;
}
return mood;
}
if (mood == MOOD_ATTACK)
return (wound && !brave) ? MOOD_ESCAPE : (!inZone ? MOOD_SLEEP : mood);
return brave ? MOOD_STALK : mood;
}
bool think(bool fixedLogic) {
thinkTime += Core::deltaTime;
if (thinkTime < 1.0f / 30.0f)
return false;
thinkTime -= 1.0f / 30.0f;
if (!target) {
mood = MOOD_SLEEP;
return true;
}
int targetBoxOld = targetBox;
// update mood
bool inZone = zone == target->zone;
if (mood != MOOD_ATTACK && targetBox > -1 && !checkBox(targetBox)) {
if (!inZone)
mood = MOOD_SLEEP;
targetBox = -1;
}
mood = target->health <= 0 ? MOOD_SLEEP : (ai == AI_FIXED ? getMoodFixed() : getMoodRandom());
// set behavior and target
int box;
switch (mood) {
case MOOD_SLEEP :
if (targetBox == -1 && checkBox(box = getRandomZoneBox()) && isStalkBox(box)) {
mood = MOOD_STALK;
gotoBox(box);
}
break;
case MOOD_STALK :
if ((targetBox == -1 || !isStalkBox(targetBox)) && checkBox(box = getRandomZoneBox())) {
if (isStalkBox(box))
gotoBox(box);
else
if (targetBox == -1) {
if (!inZone)
mood = MOOD_SLEEP;
gotoBox(box);
}
}
break;
case MOOD_ATTACK :
if (randf() > aggression)
break;
targetBox = -1;
break;
case MOOD_ESCAPE :
if (targetBox == -1 && checkBox(box = getRandomZoneBox())) {
if (isEscapeBox(box))
gotoBox(box);
else
if (inZone && isStalkBox(box)) {
mood = MOOD_STALK;
gotoBox(box);
}
}
break;
}
if (targetBox == -1)
gotoBox(target->box);
if (path && this->box != path->boxes[path->index - 1] && this->box != path->boxes[path->index])
targetBoxOld = -1;
if (targetBoxOld != targetBox) {
if (findPath(stepHeight, dropHeight))
nextWaypoint();
else
targetBox = -1;
}
if (targetBox != -1 && path) {
vec3 d = pos - waypoint;
if (fabsf(d.x) < 512 && fabsf(d.y) < 512 && fabsf(d.z) < 512)
nextWaypoint();
}
return true;
}
void nextWaypoint() {
if (!path->getNextPoint(level, waypoint))
waypoint = target->pos;
if (flying) {
if (target->stand != STAND_ONWATER)
waypoint.y -= 765.0f;
else
waypoint.y -= 64.0f;
}
}
uint16 getRandomZoneBox() {
return game->getRandomBox(zone, getZones());
}
void gotoBox(int box) {
targetBox = box;
}
bool checkBox(int box) {
if (zone != getZones()[box])
return false;
TR::Entity &e = getEntity();
TR::Box &b = game->getLevel()->boxes[box];
TR::Entity::Type type = e.type;
if (type == TR::Entity::ENEMY_REX || type == TR::Entity::ENEMY_MUTANT_1 || type == TR::Entity::ENEMY_CENTAUR) {
if (b.overlap.blockable)
return false;
} else
if (b.overlap.block)
return false;
return e.x < int(b.minX) || e.x > int(b.maxX) || e.z < int(b.minZ) || e.z > int(b.maxZ);
}
bool isStalkBox(int box) {
TR::Entity &t = target->getEntity();
TR::Box &b = game->getLevel()->boxes[box];
int x = (b.minX + b.maxX) / 2 - t.x;
if (abs(x) > STALK_BOX) return false;
int z = (b.minZ + b.maxZ) / 2 - t.z;
if (abs(z) > STALK_BOX) return false;
// TODO: check for some quadrant shit
return true;
}
bool isEscapeBox(int box) {
TR::Entity &e = getEntity();
TR::Entity &t = target->getEntity();
TR::Box &b = game->getLevel()->boxes[box];
int x = (b.minX + b.maxX) / 2 - t.x;
if (abs(x) < ESCAPE_BOX) return false;
int z = (b.minZ + b.maxZ) / 2 - t.z;
if (abs(z) < ESCAPE_BOX) return false;
return !((e.x > t.x) ^ (x > 0)) || !((e.z > t.z) ^ (z > 0));
}
bool findPath(int ascend, int descend) {
delete path;
path = NULL;
uint16 *boxes;
uint16 count = game->findPath(ascend, descend, box, targetBox, getZones(), &boxes);
if (count) {
path = new Path(level, boxes, count);
return true;
}
return false;
}
};
#define WOLF_TURN_FAST (DEG2RAD * 150)
#define WOLF_TURN_SLOW (DEG2RAD * 60)
#define WOLF_DIST_STALK STALK_BOX
#define WOLF_DIST_BITE 345
#define WOLF_DIST_ATTACK (1024 + 512)
#define WOLF_TURN_FAST PI
#define WOLF_TURN_SLOW (PI / 3.0f)
#define WOLF_TILT_MAX (PI / 6.0f)
#define WOLF_TILT_SPEED WOLF_TILT_MAX
struct Wolf : Enemy {
@@ -153,69 +481,109 @@ struct Wolf : Enemy {
};
enum {
STATE_STOP = 1,
STATE_WALK = 2,
STATE_RUN = 3,
STATE_STALKING = 5,
STATE_JUMP = 6,
STATE_HOWL = 7,
STATE_SLEEP = 8,
STATE_GROWL = 9,
STATE_TURN = 10,
STATE_DEATH = 11,
STATE_BITE = 12,
STATE_NONE ,
STATE_STOP ,
STATE_WALK ,
STATE_RUN ,
STATE_JUMP , // unused
STATE_STALK ,
STATE_ATTACK ,
STATE_HOWL ,
STATE_SLEEP ,
STATE_GROWL ,
STATE_TURN , // unused
STATE_DEATH ,
STATE_BITE ,
};
enum {
JOINT_CHEST = 2,
JOINT_HEAD = 3
};
Wolf(IGame *game, int entity) : Enemy(game, entity, 6) {}
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 0.25f) {
dropHeight = -1024;
jointChest = 2;
jointHead = 3;
nextState = STATE_NONE;
}
virtual int getStateGround() {
TR::Entity &e = getEntity();
if (!e.flags.active)
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
if (!think(false))
return state;
float angle, dist;
getTargetInfo(0, NULL, NULL, &angle, NULL);
dist = (target && target->health > 0) ? (pos - target->pos).length() : +INF;
bool inZone = target ? target->zone == zone : false;
if (nextState == state)
nextState = STATE_NONE;
switch (state) {
case STATE_SLEEP : return (target > -1 && level->entities[target].room == getRoomIndex()) ? STATE_STOP : state;
case STATE_STOP : return target > -1 ? STATE_HOWL : STATE_SLEEP;
case STATE_HOWL : return state;
case STATE_GROWL : return target > -1 ? (randf() > 0.5f ? STATE_STALKING : STATE_RUN) : STATE_STOP;
case STATE_RUN :
case STATE_STALKING : {
if (state == STATE_STALKING && health < 6) return STATE_RUN;
float angleY, dist;
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
input = turn(angleY, w); // also set input mask (left, right) for tilt control
if ((state == STATE_STALKING && dist < 512)) {
bitten = false;
return STATE_BITE;
case STATE_SLEEP :
if (mood == MOOD_ESCAPE || inZone) {
nextState = STATE_GROWL;
return STATE_STOP;
}
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
bitten = false;
return STATE_JUMP;
}
} else {
target = -1;
return STATE_GROWL;
if (randf() < 0.0001f) {
nextState = STATE_WALK;
return STATE_STOP;
}
break;
case STATE_STOP : return nextState != STATE_NONE ? nextState : STATE_WALK;
case STATE_WALK :
if (mood != MOOD_SLEEP) {
nextState = STATE_NONE;
return STATE_STALK;
}
if (randf() < 0.0001f) {
nextState = STATE_SLEEP;
return STATE_STOP;
}
if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) {
float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
}
if (state == STATE_JUMP)
break;
case STATE_GROWL :
if (nextState != STATE_NONE) return nextState;
if (mood == MOOD_ESCAPE) return STATE_RUN;
if (dist < WOLF_DIST_BITE) return STATE_BITE;
if (mood == MOOD_STALK) return STATE_STALK;
if (mood == MOOD_SLEEP) return STATE_STOP;
return STATE_RUN;
case STATE_STALK :
if (mood == MOOD_ESCAPE) return STATE_RUN;
if (dist < WOLF_DIST_BITE) return STATE_BITE;
if (dist > WOLF_DIST_STALK) return STATE_RUN;
if (mood == MOOD_ATTACK) return STATE_RUN;
if (randf() < 0.012f) {
nextState = STATE_HOWL;
return STATE_GROWL;
}
if (mood == MOOD_SLEEP) return STATE_GROWL;
break;
case STATE_RUN :
if (dist < WOLF_DIST_ATTACK) {
if (dist < WOLF_DIST_ATTACK * 0.5f && fabsf(angle) < PI * 0.5f) {
nextState = STATE_NONE;
return STATE_ATTACK;
}
nextState = STATE_STALK;
return STATE_GROWL;
}
if (mood == MOOD_STALK && dist < WOLF_DIST_STALK) {
nextState = STATE_STALK;
return STATE_GROWL;
}
if (mood == MOOD_SLEEP) return STATE_GROWL;
break;
case STATE_ATTACK :
case STATE_BITE :
if (nextState == STATE_NONE && target->health > 0 && collide(target)) {
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50 : 100);
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
}
return state == STATE_ATTACK ? STATE_RUN : state;
}
return state;
}
@@ -230,18 +598,30 @@ struct Wolf : Enemy {
}
virtual void updatePosition() {
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
float angleY = 0.0f;
if (state == STATE_RUN || state == STATE_WALK || state == STATE_STALK)
getTargetInfo(0, NULL, NULL, &angleY, NULL);
turn(angleY, state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW);
if (state == STATE_DEATH) {
animation.overrideMask = 0;
return;
}
Enemy::updatePosition();
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
lookAt(target, JOINT_CHEST, JOINT_HEAD);
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_STALK, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead);
}
};
#define BEAR_DIST_EAT 768
#define BEAR_DIST_HOWL 2048
#define BEAR_DIST_BITE 1024
#define BEAR_DIST_ATTACK 600
#define BEAR_TURN_FAST (DEG2RAD * 150)
#define BEAR_TURN_SLOW (DEG2RAD * 60)
struct Bear : Enemy {
@@ -251,82 +631,136 @@ struct Bear : Enemy {
};
enum {
STATE_WALK = 0,
STATE_STOP = 1,
STATE_HIND = 2,
STATE_RUN = 3,
STATE_HOWL = 4,
STATE_GROWL = 5,
STATE_BITE = 6,
STATE_ATTACK = 7,
STATE_EAT = 8,
STATE_DEATH = 9,
STATE_NONE = -1,
STATE_WALK ,
STATE_STOP ,
STATE_HIND ,
STATE_RUN ,
STATE_HOWL ,
STATE_GROWL ,
STATE_BITE ,
STATE_ATTACK ,
STATE_EAT ,
STATE_DEATH ,
};
enum {
JOINT_CHEST = 2,
JOINT_HEAD = 3
};
Bear(IGame *game, int entity) : Enemy(game, entity, 20) {}
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 0.5f) {
jointChest = 13;
jointHead = 14;
nextState = STATE_NONE;
}
virtual int getStateGround() {
if (!getEntity().flags.active)
return state;
if (!think(false))
return state;
if (nextState == state)
nextState = STATE_NONE;
float dist = target ? (pos - target->pos).length() : +INF;
bool targetDead = target->health <= 0;
switch (state) {
case STATE_STOP : return STATE_RUN;
case STATE_GROWL : return state;
case STATE_WALK :
case STATE_RUN :
case STATE_HIND : {
if (state == STATE_HIND && health < 6) return STATE_RUN;
float angleY, dist;
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
input = turn(angleY, w); // also set input mask (left, right) for tilt control
if ((state == STATE_HIND && dist < 512)) {
bitten = false;
return STATE_ATTACK;
}
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
bitten = false;
return STATE_BITE;
}
if (targetDead && collide(target))
return STATE_STOP; // eat lara! >:E
else
if (mood != MOOD_SLEEP) {
if (mood == MOOD_ESCAPE)
nextState = STATE_NONE;
return STATE_STOP;
} else if (randf() < 0.003f) {
nextState = STATE_GROWL;
return STATE_STOP;
}
break;
case STATE_STOP :
if (targetDead)
return dist <= BEAR_DIST_EAT ? STATE_EAT : STATE_WALK;
else
return nextState != STATE_NONE ? nextState : (mood == MOOD_SLEEP ? STATE_WALK : STATE_RUN);
case STATE_HIND :
if (collide(target)) {
return STATE_HOWL;
}
if (mood == MOOD_ESCAPE) {
nextState = STATE_NONE;
return STATE_HOWL;
}
if ((state == STATE_ATTACK || state == STATE_BITE) && !bitten) {
float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
if (mood == MOOD_SLEEP || randf() < 0.003f) {
nextState = STATE_GROWL;
return STATE_HOWL;
}
if (dist > BEAR_DIST_HOWL || randf() < 0.05f) {
nextState = STATE_STOP;
return STATE_HOWL;
}
break;
case STATE_RUN :
if (collide(target))
target->hit(3, this);
if (targetDead || mood == MOOD_SLEEP)
return STATE_STOP;
if (dist < BEAR_DIST_HOWL && randf() < 0.025f) {
nextState = STATE_HOWL;
return STATE_STOP;
} else if (dist < BEAR_DIST_BITE) {
nextState = STATE_NONE;
return STATE_BITE;
}
break;
case STATE_HOWL :
if (nextState != STATE_NONE) return nextState;
if (mood == MOOD_SLEEP || mood == MOOD_ESCAPE) return STATE_STOP;
if (dist < BEAR_DIST_ATTACK) return STATE_ATTACK;
return STATE_HIND;
case STATE_BITE :
case STATE_ATTACK :
if (nextState == STATE_NONE && collide(target)) {
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200 : 400);
nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
}
break;
}
return state;
}
virtual int getStateDeath() {
return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
switch (state) {
case STATE_HIND : return STATE_HOWL;
case STATE_RUN :
case STATE_WALK : return STATE_STOP;
case STATE_HOWL :
case STATE_STOP : return STATE_DEATH;
}
return state;// == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
}
virtual void updatePosition() {
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
Enemy::updatePosition();
/*
float angleY = 0.0f;
if (state == STATE_RUN || state == STATE_WALK || state == STATE_HIND)
getTargetInfo(0, NULL, NULL, &angleY, NULL);
turn(angleY, state == STATE_RUN ? BEAR_TURN_FAST : BEAR_TURN_SLOW);
if (state == STATE_DEATH) {
animation.overrideMask = 0;
return;
}
Enemy::updatePosition();
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
lookAt(target, JOINT_CHEST, JOINT_HEAD);
*/
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead);
}
};
#define BAT_TURN_SPEED PI
#define BAT_TURN_SPEED (DEG2RAD * 300)
#define BAT_LIFT_SPEED 512.0f
struct Bat : Enemy {
@@ -336,14 +770,20 @@ struct Bat : Enemy {
};
enum {
STATE_AWAKE = 1,
STATE_FLY = 2,
STATE_ATTACK = 3,
STATE_CIRCLING = 4,
STATE_DEATH = 5,
STATE_NONE,
STATE_AWAKE,
STATE_FLY,
STATE_ATTACK,
STATE_CIRCLING,
STATE_DEATH,
};
Bat(IGame *game, int entity) : Enemy(game, entity, 1) { stand = STAND_AIR; }
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.03f) {
stand = STAND_AIR;
stepHeight = 20 * 1024;
dropHeight = -20 * 1024;
jointHead = 4;
}
virtual int getStateAir() {
if (!getEntity().flags.active) {
@@ -352,29 +792,24 @@ struct Bat : Enemy {
return STATE_AWAKE;
}
if (!think(false))
return state;
switch (state) {
case STATE_AWAKE : return STATE_FLY;
case STATE_ATTACK :
case STATE_FLY : {
vec3 p;
float angleY, dist;
if (getTargetInfo(765, &p, NULL, &angleY, &dist)) {
turn(angleY, BAT_TURN_SPEED);
lift(p.y - pos.y, BAT_LIFT_SPEED);
if (dist < 128) {
if (state == STATE_ATTACK && !(animation.frameIndex % 15))
bite(pos, 2); // TODO: bite position
else
bitten = false;
return STATE_ATTACK;
if (!collide(target)) {
mood = MOOD_SLEEP;
return STATE_FLY;
} else
return STATE_FLY;
} else {
turn(PI, BAT_TURN_SPEED); // circling
return STATE_FLY;
}
bite(animation.getJoints(getMatrix(), jointHead, true).pos, 2);
break;
case STATE_FLY :
if (collide(target)) {
mood = MOOD_ATTACK;
return STATE_ATTACK;
}
break;
}
return state;
@@ -391,6 +826,15 @@ struct Bat : Enemy {
velocity = vec3(0.0f, velocity.y + GRAVITY * Core::deltaTime, 0.0f);
}
virtual void updatePosition() {
float angleY = 0.0f;
if (state == STATE_FLY || state == STATE_ATTACK)
getTargetInfo(0, NULL, NULL, &angleY, NULL);
turn(angleY, BAT_TURN_SPEED);
if (flying)
lift(waypoint.y - pos.y, BAT_LIFT_SPEED);
Enemy::updatePosition();
}
};
#endif

View File

@@ -216,6 +216,10 @@ struct Lara : Character {
int roomPrev; // water out from room
vec2 rotFactor;
float hitTime;
int hitDir;
vec3 collisionOffset;
struct Braid {
Lara *lara;
vec3 offset;
@@ -386,6 +390,9 @@ struct Lara : Character {
animation.setAnim(ANIM_STAND);
}
hitDir = -1;
hitTime = 0.0f;
getEntity().flags.active = 1;
initMeshOverrides();
@@ -888,8 +895,9 @@ struct Lara : Character {
}
void updateOverrides() {
int overrideMask = 0;
// head & chest
animation.overrideMask |= BODY_CHEST | BODY_HEAD;
overrideMask |= BODY_CHEST | BODY_HEAD;
animation.overrides[ 7] = animation.getJointRot( 7);
animation.overrides[14] = animation.getJointRot(14);
@@ -902,6 +910,7 @@ struct Lara : Character {
}
*/
// arms
if (!emptyHands()) {
// right arm
Arm *arm = &arms[0];
@@ -914,10 +923,38 @@ struct Lara : Character {
animation.overrides[12] = arm->animation.getJointRot(12);
animation.overrides[13] = arm->animation.getJointRot(13);
animation.overrideMask |= (BODY_ARM_R | BODY_ARM_L);
overrideMask |= (BODY_ARM_R | BODY_ARM_L);
} else
animation.overrideMask &= ~(BODY_ARM_R | BODY_ARM_L);
overrideMask &= ~(BODY_ARM_R | BODY_ARM_L);
// update hit anim
if (hitDir >= 0) {
Animation hitAnim = Animation(level, getModel());
switch (hitDir) {
case 0 : hitAnim.setAnim(ANIM_HIT_FRONT, 0, false); break;
case 1 : hitAnim.setAnim(ANIM_HIT_LEFT, 0, false); break;
case 2 : hitAnim.setAnim(ANIM_HIT_BACK , 0, false); break;
case 3 : hitAnim.setAnim(ANIM_HIT_RIGHT, 0, false); break;
}
hitTime = min(hitTime, hitAnim.timeMax - EPS);
hitAnim.time = hitTime;
hitAnim.updateInfo();
overrideMask &= ~(BODY_CHEST | BODY_HEAD);
int hitMask = (BODY_UPPER | BODY_LOWER | BODY_HEAD) & ~overrideMask;
int index = 0;
while (hitMask) {
if (hitMask & 1)
animation.overrides[index] = hitAnim.getJointRot(index);
index++;
hitMask >>= 1;
}
hitTime += Core::deltaTime;
overrideMask = BODY_UPPER | BODY_LOWER | BODY_HEAD;
}
animation.overrideMask = overrideMask;
lookAt(viewTarget);
@@ -1000,16 +1037,16 @@ struct Lara : Character {
int getTarget() {
vec3 dir = getDir().normal();
float dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST;
float dist = TARGET_MAX_DIST;
int index = -1;
for (int i = 0; i < level->entitiesCount; i++) {
TR::Entity &e = level->entities[i];
if (!e.flags.active || !e.isEnemy()) continue;
Character *controller = (Character*)e.controller;
if (controller->health <= 0) continue;
Character *enemy = (Character*)e.controller;
if (enemy->health <= 0) continue;
vec3 p = controller->pos;
vec3 p = enemy->getBoundingBox().center();
vec3 v = p - pos;
if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees
@@ -1078,8 +1115,6 @@ struct Lara : Character {
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
if (enemy && health > 0)
playSound(TR::SND_HIT, pos, Sound::PAN | Sound::REPLAY);
};
bool waterOut() {
@@ -1729,6 +1764,12 @@ struct Lara : Character {
}
}
virtual void update() {
collisionOffset = vec3(0.0f);
checkCollisions();
Character::update();
}
virtual void updateAnimation(bool commands) {
Controller::updateAnimation(commands);
updateWeapon();
@@ -1858,7 +1899,7 @@ struct Lara : Character {
vTilt *= rotFactor.y;
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
if (velocity.length() >= 1.0f)
if ((velocity + collisionOffset).length2() >= 1.0f)
move();
if (getEntity().type != TR::Entity::LARA) {
@@ -1879,32 +1920,47 @@ struct Lara : Character {
return getEntity().type == TR::Entity::LARA ? pos : chestOffset;
}
void move() {
//TR::Entity &e = getEntity();
//TR::Level::FloorInfo info;
//float f, c;
//bool canPassGap = true;
/*
if (velocity != 0.0f) {
vec3 dir = velocity.normal() * 128.0f;
vec3 p = pos + dir;
level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info);
if (info.floor < p.y - (256 + 128) || info.ceiling > p.y - 768) { // wall
vec3 axis = dir.axisXZ();
vec3 normal = (p - vec3(int(p.x / 1024.0f) * 1024.0f + 512.0f, p.y, int(p.z / 1024.0f) * 1024.0f + 512.0f)).axisXZ();
LOG("%f %f = %f %f = %f\n", axis.x, axis.z, normal.x, normal.z, abs(axis.dot(normal)));
if (abs(axis.dot(normal)) > EPS) {
canPassGap = false;
} else {
updateEntity();
checkRoom();
void checkCollisions() {
if (state == STATE_DEATH || stand != STAND_GROUND) {
hitDir = -1;
return;
}
// check enemies
for (int i = 0; i < level->entitiesCount; i++) {
TR::Entity &e = level->entities[i];
if (!e.flags.active || !e.isEnemy()) continue;
Character *enemy = (Character*)e.controller;
if (enemy->health <= 0) continue;
vec3 dir = pos - enemy->pos;
vec3 p = dir.rotateY(-enemy->angle.y);
Box enemyBox = enemy->getBoundingBoxLocal();
if (!enemyBox.contains(p))
continue;
// get shift
p = enemyBox.closestPoint2D(p);
p = (p.rotateY(enemy->angle.y) + enemy->pos) - pos;
collisionOffset += vec3(p.x, 0.0f, p.z);
// get hit dir
if (hitDir == -1) {
if (health > 0)
playSound(TR::SND_HIT, pos, Sound::PAN);
hitTime = 0.0f;
}
hitDir = angleQuadrant(dir.rotateY(angle.y + PI * 0.5f).angleY());
return;
}
*/
vec3 vel = velocity * Core::deltaTime * 30.0f;
hitDir = -1;
}
void move() {
vec3 vel = velocity * Core::deltaTime * 30.0f + collisionOffset;
vec3 opos(pos), offset(0.0f);
float radius = stand == STAND_UNDERWATER ? LARA_RADIUS_WATER : LARA_RADIUS;

View File

@@ -34,6 +34,7 @@ struct Level : IGame {
ShaderCache *shaderCache;
AmbientCache *ambientCache;
WaterCache *waterCache;
ZoneCache *zoneCache;
Sound::Sample *sndAmbient;
Sound::Sample *sndUnderwater;
@@ -51,6 +52,15 @@ struct Level : IGame {
return camera;
}
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) {
ZoneCache::Item *item = zoneCache->getBoxes(zone, zones);
return item->boxes[int(randf() * item->count)];
}
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
return zoneCache->findPath(ascend, descend, boxStart, boxEnd, zones, boxes);
}
virtual void setClipParams(float clipSign, float clipHeight) {
params->clipSign = clipSign;
params->clipHeight = clipHeight;
@@ -144,7 +154,7 @@ struct Level : IGame {
case TR::Entity::ENEMY_CENTAUR :
case TR::Entity::ENEMY_MUMMY :
case TR::Entity::ENEMY_LARSON :
entity.controller = new Enemy(this, i, 100);
entity.controller = new Enemy(this, i, 100, 10, 0.0f);
break;
case TR::Entity::DOOR_1 :
case TR::Entity::DOOR_2 :
@@ -223,6 +233,7 @@ struct Level : IGame {
ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL;
waterCache = Core::settings.water ? new WaterCache(this) : NULL;
zoneCache = new ZoneCache(this);
shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL;
initReflections();
@@ -253,6 +264,7 @@ struct Level : IGame {
delete shadow;
delete ambientCache;
delete waterCache;
delete zoneCache;
delete atlas;
delete cube;
@@ -809,7 +821,7 @@ struct Level : IGame {
glPopMatrix();
*/
/*
Core::setDepthTest(false);
glBegin(GL_LINES);
glColor3f(1, 1, 1);
@@ -817,11 +829,11 @@ struct Level : IGame {
glVertex3fv((GLfloat*)&lara->mainLightPos);
glEnd();
Core::setDepthTest(true);
*/
// Debug::Draw::sphere(lara->mainLightPos, lara->mainLightColor.w, vec4(1, 1, 0, 1));
Box bbox = lara->getBoundingBox();
Debug::Draw::box(bbox.min, bbox.max, vec4(1, 0, 1, 1));
// Box bbox = lara->getBoundingBox();
// Debug::Draw::box(bbox.min, bbox.max, vec4(1, 0, 1, 1));
Core::setBlending(bmAlpha);
// Debug::Level::rooms(level, lara->pos, lara->getEntity().room);
@@ -832,8 +844,10 @@ struct Level : IGame {
// Core::setDepthTest(true);
// Debug::Level::meshes(level);
// Debug::Level::entities(level);
Debug::Level::zones(level, lara);
Debug::Level::blocks(level);
// Debug::Level::zones(level, lara);
// Debug::Level::blocks(level);
// Debug::Level::path(level, (Enemy*)level.entities[86].controller);
// Debug::Level::debugOverlaps(level, lara->box);
Core::setBlending(bmNone);

View File

@@ -102,7 +102,7 @@ int angleQuadrant(float angle) {
}
float decrease(float delta, float &value, float &speed) {
if (speed > 0.0f && fabsf(delta) > 0.01f) {
if (speed > 0.0f && fabsf(delta) > 0.001f) {
if (delta > 0) speed = min(delta, speed);
if (delta < 0) speed = max(delta, -speed);
value += speed;
@@ -228,9 +228,11 @@ struct vec3 {
return vec3(x*c - z*s, y, x*s + z*c);
}
float angle(const vec3 &v) {
float angle(const vec3 &v) const {
return dot(v) / (length() * v.length());
}
float angleY() const { return atan2f(z, x); }
};
struct vec4 {
@@ -806,6 +808,29 @@ struct Box {
}
}
bool contains(const vec3 &v) const {
return v.x >= min.x && v.x <= max.x && v.y >= min.y && v.y <= max.x && v.z >= min.z && v.z <= max.z;
}
vec3 closestPoint2D(const vec3 &v) const {
float ax = v.x - min.x;
float bx = max.x - v.x;
float az = v.z - min.z;
float bz = max.z - v.z;
vec3 p = v;
if (ax <= bx && ax <= az && ax <= bz)
p.x -= ax;
else if (bx <= ax && bx <= az && bx <= bz)
p.x += bx;
else if (az <= ax && az <= bx && az <= bz)
p.z -= az;
else
p.z += bz;
return p;
}
bool intersect(const Box &box) const {
return !((max.x < box.min.x || min.x > box.max.x) || (max.y < box.min.y || min.y > box.max.y) || (max.z < box.min.z || min.z > box.max.z));
}
@@ -827,6 +852,20 @@ struct Box {
}
};
struct Sphere {
vec3 center;
float radius;
Sphere() {}
Sphere(const vec3 &center, float radius) : center(center), radius(radius) {}
bool intersect(const Sphere &s) {
float d = (center - s.center).length2();
float r = (radius + s.radius);
return d < r * r;
}
};
struct Stream {
static char cacheDir[255];
static char contentDir[255];