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:
158
src/cache.h
158
src/cache.h
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
118
src/debug.h
118
src/debug.h
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
754
src/enemy.h
754
src/enemy.h
@@ -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
|
||||
|
120
src/lara.h
120
src/lara.h
@@ -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;
|
||||
|
28
src/level.h
28
src/level.h
@@ -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);
|
||||
|
||||
|
43
src/utils.h
43
src/utils.h
@@ -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 ¢er, 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];
|
||||
|
Reference in New Issue
Block a user