mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-14 00:54:05 +02:00
This commit is contained in:
@@ -798,7 +798,7 @@ struct ZoneCache {
|
||||
return items = new Item(zone, count, zones, boxes, items);
|
||||
}
|
||||
|
||||
uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
|
||||
uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
|
||||
if (boxStart == 0xFFFF || boxEnd == 0xFFFF)
|
||||
return 0;
|
||||
|
||||
@@ -859,6 +859,9 @@ struct ZoneCache {
|
||||
// has same zone
|
||||
if (zones[index] != zone)
|
||||
continue;
|
||||
// check passability
|
||||
if (big && level->boxes[index].overlap.blockable)
|
||||
continue;
|
||||
// check blocking (doors)
|
||||
if (level->boxes[index].overlap.block)
|
||||
continue;
|
||||
|
12
src/camera.h
12
src/camera.h
@@ -28,6 +28,8 @@ struct Camera : Controller {
|
||||
bool firstPerson;
|
||||
bool isVR;
|
||||
|
||||
float shake;
|
||||
|
||||
Camera(IGame *game, Lara *owner) : Controller(game, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(0.0f), actTargetEntity(-1), actCamera(-1), reflectPlane(NULL), isVR(false) {
|
||||
changeView(false);
|
||||
cutscene = owner->getEntity().type != TR::Entity::LARA && level->cameraFrames;
|
||||
@@ -90,6 +92,9 @@ struct Camera : Controller {
|
||||
}
|
||||
|
||||
virtual void update() {
|
||||
if (shake > 0.0f)
|
||||
shake = max(0.0f, shake - Core::deltaTime);
|
||||
|
||||
#ifndef LEVEL_EDITOR
|
||||
if (cutscene) { // cutscene
|
||||
timer += Core::deltaTime * 30;
|
||||
@@ -149,8 +154,8 @@ struct Camera : Controller {
|
||||
advAngle.y = lerp(clampAngle(advAngle.y), 0.0f, t);
|
||||
}
|
||||
#endif
|
||||
|
||||
angle = owner->angle + advAngle;
|
||||
if (owner->health > 0)
|
||||
angle = owner->angle + advAngle;
|
||||
angle.z = 0.0f;
|
||||
|
||||
if (owner->stand == Lara::STAND_ONWATER)
|
||||
@@ -275,13 +280,14 @@ struct Camera : Controller {
|
||||
Core::mViewInv = mViewInv;
|
||||
|
||||
Core::mView = Core::mViewInv.inverse();
|
||||
if (shake > 0.0f)
|
||||
Core::mView.translate(vec3(0.0f, sinf(shake * PI * 7) * shake * 48.0f, 0.0f));
|
||||
|
||||
if (isVR)
|
||||
Core::mView.translate(Core::mViewInv.right.xyz * (-Core::eye * 32.0f));
|
||||
|
||||
Core::mProj = getProjMatrix();
|
||||
|
||||
// TODO: camera shake
|
||||
// TODO: temporal anti-aliasing
|
||||
// Core::mProj.e02 = (randf() - 0.5f) * 32.0f / Core::width;
|
||||
// Core::mProj.e12 = (randf() - 0.5f) * 32.0f / Core::height;
|
||||
|
@@ -20,7 +20,7 @@ struct IGame {
|
||||
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 uint16 findPath(int ascend, int descend, bool big, 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() {}
|
||||
@@ -28,6 +28,7 @@ struct IGame {
|
||||
virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {}
|
||||
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
|
||||
virtual void renderCompose(int roomIndex, bool genShadowMask = false) {}
|
||||
virtual void fxQuake(float time) {}
|
||||
};
|
||||
|
||||
struct Controller {
|
||||
@@ -265,14 +266,14 @@ struct Controller {
|
||||
|
||||
}
|
||||
|
||||
bool collide(Controller *controller) {
|
||||
int collide(Controller *controller, bool checkBoxes = true) {
|
||||
TR::Model *a = getModel();
|
||||
TR::Model *b = getModel();
|
||||
TR::Model *b = controller->getModel();
|
||||
if (!a || !b)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
if (!getBoundingBox().intersect(controller->getBoundingBox()))
|
||||
return false;
|
||||
if (checkBoxes && !getBoundingBox().intersect(controller->getBoundingBox()))
|
||||
return 0;
|
||||
|
||||
ASSERT(a->mCount <= 34);
|
||||
ASSERT(b->mCount <= 34);
|
||||
@@ -283,12 +284,15 @@ struct Controller {
|
||||
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;
|
||||
int mask = 0;
|
||||
for (int i = 0; i < a->mCount; i++)
|
||||
if (aSpheres[i].radius > 0.0f)
|
||||
for (int j = 0; j < b->mCount; j++)
|
||||
if (bSpheres[j].radius > 0.0f && bSpheres[j].intersect(aSpheres[i])) {
|
||||
mask |= (1 << i);
|
||||
break;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
|
||||
@@ -452,6 +456,7 @@ struct Controller {
|
||||
if (cmd == TR::ANIM_CMD_EFFECT) {
|
||||
switch (fx) {
|
||||
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break;
|
||||
case TR::EFFECT_FLOOR_SHAKE : game->fxQuake(0.5f * max(0.0f, 1.0f - (pos - ((Controller*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break;
|
||||
case TR::EFFECT_LARA_BUBBLES : doBubbles(); break;
|
||||
default : cmdEffect(fx); break;
|
||||
}
|
||||
|
19
src/debug.h
19
src/debug.h
@@ -496,16 +496,29 @@ namespace Debug {
|
||||
TR::Model *m = controller->getModel();
|
||||
if (!m) continue;
|
||||
|
||||
Box box = controller->getBoundingBoxLocal();
|
||||
Debug::Draw::box(matrix, box.min, box.max, vec4(1.0));
|
||||
bool bboxIntersect = false;
|
||||
|
||||
Sphere spheres[34];
|
||||
ASSERT(m->mCount <= 34);
|
||||
controller->getSpheres(spheres);
|
||||
int mask = 0;
|
||||
for (int j = 0; j < level.entitiesCount; j++) {
|
||||
TR::Entity &t = level.entities[j];
|
||||
if (j == i || ((!t.isEnemy() || !t.flags.active) && t.type != TR::Entity::LARA)) continue;
|
||||
Controller *enemy = (Controller*)t.controller;
|
||||
if (!controller->getBoundingBox().intersect(enemy->getBoundingBox()))
|
||||
continue;
|
||||
bboxIntersect = true;
|
||||
mask |= controller->collide(enemy);
|
||||
}
|
||||
|
||||
Box box = controller->getBoundingBoxLocal();
|
||||
Debug::Draw::box(matrix, box.min, box.max, bboxIntersect ? vec4(1, 0, 0, 1): vec4(1));
|
||||
|
||||
|
||||
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));
|
||||
Debug::Draw::sphere(sphere.center, sphere.radius, (mask & (1 << joint)) ? vec4(1, 0, 0, 0.5f) : vec4(0, 1, 1, 0.5f));
|
||||
/*
|
||||
{ //if (e.id != 0) {
|
||||
char buf[255];
|
||||
|
384
src/enemy.h
384
src/enemy.h
@@ -54,6 +54,7 @@ struct Enemy : Character {
|
||||
vec3 waypoint;
|
||||
|
||||
float thinkTime;
|
||||
float length; // dist from center to head (jaws)
|
||||
float aggression;
|
||||
int radius;
|
||||
int stepHeight;
|
||||
@@ -65,11 +66,20 @@ struct Enemy : Character {
|
||||
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) {
|
||||
float targetDist;
|
||||
bool targetDead;
|
||||
bool targetInView; // target in enemy view zone
|
||||
bool targetFromView; // enemy in target view zone
|
||||
bool targetCanAttack;
|
||||
|
||||
Enemy(IGame *game, int entity, int health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) {
|
||||
stepHeight = 256;
|
||||
dropHeight = -256;
|
||||
|
||||
jointChest = jointHead = -1;
|
||||
|
||||
targetDist = +INF;
|
||||
targetInView = targetFromView = targetCanAttack = false;
|
||||
}
|
||||
|
||||
virtual ~Enemy() {
|
||||
@@ -105,11 +115,14 @@ struct Enemy : Character {
|
||||
if (a.contains(x, z))
|
||||
return true;
|
||||
|
||||
bool big = getEntity().isBigEnemy();
|
||||
TR::Overlap *o = &level->overlaps[a.overlap.index];
|
||||
do {
|
||||
TR::Box &b = level->boxes[o->boxIndex];
|
||||
if (!b.contains(x, z))
|
||||
continue;
|
||||
if (big && b.overlap.blockable)
|
||||
continue;
|
||||
if (getZones()[o->boxIndex] == zone) {
|
||||
int d = a.floor - b.floor;
|
||||
if (d <= stepHeight && d >= dropHeight)
|
||||
@@ -181,12 +194,12 @@ struct Enemy : Character {
|
||||
animation.overrideMask &= ~(1 << chest);
|
||||
}
|
||||
|
||||
void lookAt(int target, int chest, int head) {
|
||||
void lookAt(int target, int chest, int head, bool rotate = false) {
|
||||
float speed = 8.0f * Core::deltaTime;
|
||||
quat rot;
|
||||
|
||||
if (chest > -1) {
|
||||
if (aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot))
|
||||
if (rotate && aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot))
|
||||
rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed);
|
||||
else
|
||||
rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed);
|
||||
@@ -194,7 +207,7 @@ struct Enemy : Character {
|
||||
}
|
||||
|
||||
if (head > -1) {
|
||||
if (aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot))
|
||||
if (rotate && aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot))
|
||||
rotHead = rotHead.slerp(rot, speed);
|
||||
else
|
||||
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
|
||||
@@ -244,7 +257,6 @@ struct Enemy : Character {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
virtual void hit(int damage, Controller *enemy = NULL) {
|
||||
Character::hit(damage, enemy);
|
||||
wound = true;
|
||||
@@ -301,9 +313,18 @@ struct Enemy : Character {
|
||||
|
||||
if (!target) {
|
||||
mood = MOOD_SLEEP;
|
||||
targetDist = +INF;
|
||||
targetInView = targetFromView = targetCanAttack = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
vec3 targetVec = target->pos - pos - getDir() * length;
|
||||
targetDist = targetVec.length();
|
||||
targetDead = target->health <= 0;
|
||||
targetInView = targetVec.dot(getDir()) > 0;
|
||||
targetFromView = targetVec.dot(target->getDir()) < 0;
|
||||
targetCanAttack = targetInView && fabsf(targetVec.y) <= 256.0f;
|
||||
|
||||
int targetBoxOld = targetBox;
|
||||
|
||||
// update mood
|
||||
@@ -364,7 +385,7 @@ struct Enemy : Character {
|
||||
targetBoxOld = -1;
|
||||
|
||||
if (targetBoxOld != targetBox) {
|
||||
if (findPath(stepHeight, dropHeight))
|
||||
if (findPath(stepHeight, dropHeight, getEntity().isBigEnemy()))
|
||||
nextWaypoint();
|
||||
else
|
||||
targetBox = -1;
|
||||
@@ -449,12 +470,12 @@ struct Enemy : Character {
|
||||
return !((e.x > t.x) ^ (x > 0)) || !((e.z > t.z) ^ (z > 0));
|
||||
}
|
||||
|
||||
bool findPath(int ascend, int descend) {
|
||||
bool findPath(int ascend, int descend, bool big) {
|
||||
delete path;
|
||||
path = NULL;
|
||||
|
||||
uint16 *boxes;
|
||||
uint16 count = game->findPath(ascend, descend, box, targetBox, getZones(), &boxes);
|
||||
uint16 count = game->findPath(ascend, descend, big, box, targetBox, getZones(), &boxes);
|
||||
if (count) {
|
||||
path = new Path(level, boxes, count);
|
||||
return true;
|
||||
@@ -474,6 +495,10 @@ struct Enemy : Character {
|
||||
|
||||
struct Wolf : Enemy {
|
||||
|
||||
enum {
|
||||
HIT_MASK = 0x774F, // body, head, front legs
|
||||
};
|
||||
|
||||
enum {
|
||||
ANIM_DEATH = 20,
|
||||
ANIM_DEATH_RUN = 21,
|
||||
@@ -496,7 +521,7 @@ struct Wolf : Enemy {
|
||||
STATE_BITE ,
|
||||
};
|
||||
|
||||
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 0.25f) {
|
||||
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) {
|
||||
dropHeight = -1024;
|
||||
jointChest = 2;
|
||||
jointHead = 3;
|
||||
@@ -511,11 +536,9 @@ struct Wolf : Enemy {
|
||||
if (!think(false))
|
||||
return state;
|
||||
|
||||
float angle, dist;
|
||||
float angle;
|
||||
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)
|
||||
@@ -546,15 +569,18 @@ struct Wolf : Enemy {
|
||||
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 (targetDist < WOLF_DIST_BITE && targetCanAttack) 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 (mood == MOOD_ESCAPE) return STATE_RUN;
|
||||
if (targetDist < WOLF_DIST_BITE && targetCanAttack) return STATE_BITE;
|
||||
if (targetDist > WOLF_DIST_STALK) return STATE_RUN;
|
||||
if (mood == MOOD_ATTACK) {
|
||||
if (!targetInView || targetFromView || targetDist > WOLF_DIST_ATTACK)
|
||||
return STATE_RUN;
|
||||
}
|
||||
if (randf() < 0.012f) {
|
||||
nextState = STATE_HOWL;
|
||||
return STATE_GROWL;
|
||||
@@ -562,15 +588,15 @@ struct Wolf : Enemy {
|
||||
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) {
|
||||
if (targetDist < WOLF_DIST_ATTACK && targetInView) {
|
||||
if (targetDist < WOLF_DIST_ATTACK * 0.5f && targetFromView) {
|
||||
nextState = STATE_NONE;
|
||||
return STATE_ATTACK;
|
||||
}
|
||||
nextState = STATE_STALK;
|
||||
return STATE_GROWL;
|
||||
}
|
||||
if (mood == MOOD_STALK && dist < WOLF_DIST_STALK) {
|
||||
if (mood == MOOD_STALK && targetDist < WOLF_DIST_STALK) {
|
||||
nextState = STATE_STALK;
|
||||
return STATE_GROWL;
|
||||
}
|
||||
@@ -578,7 +604,7 @@ struct Wolf : Enemy {
|
||||
break;
|
||||
case STATE_ATTACK :
|
||||
case STATE_BITE :
|
||||
if (nextState == STATE_NONE && target->health > 0 && collide(target)) {
|
||||
if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
|
||||
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50 : 100);
|
||||
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
|
||||
}
|
||||
@@ -599,6 +625,7 @@ struct Wolf : Enemy {
|
||||
|
||||
virtual void updatePosition() {
|
||||
float angleY = 0.0f;
|
||||
|
||||
if (state == STATE_RUN || state == STATE_WALK || state == STATE_STALK)
|
||||
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||
|
||||
@@ -610,7 +637,7 @@ struct Wolf : Enemy {
|
||||
}
|
||||
|
||||
Enemy::updatePosition();
|
||||
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_STALK, jointChest, jointHead);
|
||||
setOverrides(state != STATE_DEATH, jointChest, jointHead);
|
||||
lookAt(target ? target->entity : -1, jointChest, jointHead);
|
||||
}
|
||||
};
|
||||
@@ -625,6 +652,10 @@ struct Wolf : Enemy {
|
||||
|
||||
struct Bear : Enemy {
|
||||
|
||||
enum {
|
||||
HIT_MASK = 0x2406C, // front legs and head
|
||||
};
|
||||
|
||||
enum {
|
||||
ANIM_DEATH_HIND = 19,
|
||||
ANIM_DEATH = 20,
|
||||
@@ -644,7 +675,7 @@ struct Bear : Enemy {
|
||||
STATE_DEATH ,
|
||||
};
|
||||
|
||||
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 0.5f) {
|
||||
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 500.0f, 0.5f) {
|
||||
jointChest = 13;
|
||||
jointHead = 14;
|
||||
nextState = STATE_NONE;
|
||||
@@ -654,73 +685,74 @@ struct Bear : Enemy {
|
||||
if (!getEntity().flags.active)
|
||||
return state;
|
||||
|
||||
if (!think(false))
|
||||
if (!think(true))
|
||||
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_WALK :
|
||||
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;
|
||||
}
|
||||
if (nextState != STATE_NONE) return STATE_STOP;
|
||||
if (targetDead && targetInView && (collide(target) & HIT_MASK))
|
||||
return nextState = STATE_STOP; // eat lara! >:E
|
||||
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;
|
||||
return (targetDist < BEAR_DIST_EAT && targetCanAttack) ? 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) {
|
||||
if (wound) {
|
||||
nextState = STATE_NONE;
|
||||
return STATE_HOWL;
|
||||
}
|
||||
if (mood == MOOD_SLEEP || randf() < 0.003f) {
|
||||
|
||||
if (targetInView && (collide(target) & HIT_MASK)) return STATE_HOWL;
|
||||
|
||||
if (mood == MOOD_ESCAPE)
|
||||
nextState = STATE_NONE;
|
||||
else if (mood == MOOD_SLEEP || randf() < 0.003f)
|
||||
nextState = STATE_GROWL;
|
||||
return STATE_HOWL;
|
||||
}
|
||||
if (dist > BEAR_DIST_HOWL || randf() < 0.05f) {
|
||||
else if (targetDist > BEAR_DIST_HOWL || randf() < 0.05f)
|
||||
nextState = STATE_STOP;
|
||||
return STATE_HOWL;
|
||||
}
|
||||
|
||||
return STATE_HOWL;
|
||||
break;
|
||||
case STATE_RUN :
|
||||
if (collide(target))
|
||||
if (collide(target) & HIT_MASK)
|
||||
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;
|
||||
if (nextState != STATE_NONE) return STATE_STOP;
|
||||
if (targetInView) {
|
||||
if (!wound && targetDist < BEAR_DIST_HOWL && randf() < 0.025f) {
|
||||
nextState = STATE_HOWL;
|
||||
return STATE_STOP;
|
||||
}
|
||||
if (targetDist < BEAR_DIST_BITE) return STATE_BITE;
|
||||
}
|
||||
break;
|
||||
case STATE_HOWL :
|
||||
if (wound) {
|
||||
nextState = STATE_NONE;
|
||||
return STATE_STOP;
|
||||
}
|
||||
if (nextState != STATE_NONE) return nextState;
|
||||
if (mood == MOOD_SLEEP || mood == MOOD_ESCAPE) return STATE_STOP;
|
||||
if (dist < BEAR_DIST_ATTACK) return STATE_ATTACK;
|
||||
if (targetDist < BEAR_DIST_ATTACK) return STATE_ATTACK;
|
||||
return STATE_HIND;
|
||||
case STATE_BITE :
|
||||
case STATE_ATTACK :
|
||||
if (nextState == STATE_NONE && collide(target)) {
|
||||
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
|
||||
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200 : 400);
|
||||
nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
|
||||
}
|
||||
@@ -778,7 +810,7 @@ struct Bat : Enemy {
|
||||
STATE_DEATH,
|
||||
};
|
||||
|
||||
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.03f) {
|
||||
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.0f, 0.03f) {
|
||||
stand = STAND_AIR;
|
||||
stepHeight = 20 * 1024;
|
||||
dropHeight = -20 * 1024;
|
||||
@@ -837,4 +869,236 @@ struct Bat : Enemy {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#define REX_DIST_BITE 1500
|
||||
#define REX_DIST_BITE_MAX 4096
|
||||
#define REX_DIST_WALK 5120
|
||||
#define REX_TURN_FAST (DEG2RAD * 120)
|
||||
#define REX_TURN_SLOW (DEG2RAD * 60)
|
||||
|
||||
struct Rex : Enemy {
|
||||
|
||||
enum {
|
||||
HIT_MASK = (1 << 12) | (1 << 13), // head
|
||||
};
|
||||
|
||||
enum {
|
||||
STATE_NONE,
|
||||
STATE_STOP,
|
||||
STATE_WALK,
|
||||
STATE_RUN,
|
||||
STATE_UNUSED,
|
||||
STATE_DEATH,
|
||||
STATE_BAWL,
|
||||
STATE_BITE,
|
||||
STATE_FATAL,
|
||||
};
|
||||
|
||||
Rex(IGame *game, int entity) : Enemy(game, entity, 100, 341, 2000.0f, 1.0f) {
|
||||
jointChest = 10;
|
||||
jointHead = 12;
|
||||
nextState = STATE_NONE;
|
||||
}
|
||||
|
||||
virtual int getStateGround() {
|
||||
if (!getEntity().flags.active)
|
||||
return state;
|
||||
|
||||
if (!think(true))
|
||||
return state;
|
||||
|
||||
if (nextState == state)
|
||||
nextState = STATE_NONE;
|
||||
|
||||
if (targetDead) {
|
||||
return (state == STATE_STOP || state == STATE_WALK) ? STATE_WALK : STATE_STOP;
|
||||
if (state != STATE_STOP) return STATE_STOP;
|
||||
return STATE_WALK;
|
||||
}
|
||||
|
||||
int mask = collide(target);
|
||||
|
||||
// if Lara is behind and watching Rex we need to rotate
|
||||
bool walk = targetFromView && !targetInView && mood != MOOD_ESCAPE;
|
||||
if (!walk && targetCanAttack && targetDist > REX_DIST_BITE && targetDist < REX_DIST_BITE_MAX)
|
||||
walk = true;
|
||||
|
||||
switch (state) {
|
||||
case STATE_STOP :
|
||||
if (nextState != STATE_NONE) return nextState;
|
||||
if (targetCanAttack && targetDist < REX_DIST_BITE) return STATE_BITE;
|
||||
if (mood == MOOD_SLEEP || walk) return STATE_WALK;
|
||||
return STATE_RUN;
|
||||
case STATE_WALK :
|
||||
if (mask) target->hit(1, this);
|
||||
if (mood != MOOD_SLEEP && !walk) return STATE_STOP;
|
||||
if (targetInView && randf() < 0.015f) {
|
||||
nextState = STATE_BAWL;
|
||||
return STATE_STOP;
|
||||
}
|
||||
break;
|
||||
case STATE_RUN :
|
||||
if (mask) target->hit(10, this);
|
||||
if ((targetCanAttack && targetDist < REX_DIST_WALK) || walk)
|
||||
return STATE_STOP;
|
||||
if (targetInView && mood != MOOD_ESCAPE && randf() < 0.015f) {
|
||||
nextState = STATE_BAWL;
|
||||
return STATE_STOP;
|
||||
}
|
||||
if (mood == MOOD_SLEEP)
|
||||
return STATE_STOP;
|
||||
break;
|
||||
case STATE_BITE :
|
||||
if (mask & HIT_MASK) {
|
||||
target->hit(10000, this);
|
||||
return STATE_FATAL;
|
||||
}
|
||||
nextState = STATE_WALK;
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
virtual int getStateDeath() {
|
||||
return state == STATE_STOP ? STATE_DEATH : STATE_STOP;
|
||||
}
|
||||
|
||||
virtual void updatePosition() {
|
||||
if (state == STATE_DEATH) {
|
||||
animation.overrideMask = 0;
|
||||
angle.z = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float angleY = 0.0f;
|
||||
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||
|
||||
if (state == STATE_RUN || state == STATE_WALK)
|
||||
turn(angleY, state == STATE_RUN ? REX_TURN_FAST : REX_TURN_SLOW);
|
||||
|
||||
Enemy::updatePosition();
|
||||
setOverrides(true, jointChest, jointHead);
|
||||
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH && state != STATE_FATAL);
|
||||
}
|
||||
};
|
||||
|
||||
#define RAPTOR_DIST_BITE 680
|
||||
#define RAPTOR_DIST_ATTACK (1024 + 512)
|
||||
|
||||
#define RAPTOR_TURN_FAST (DEG2RAD * 120)
|
||||
#define RAPTOR_TURN_SLOW (DEG2RAD * 30)
|
||||
|
||||
struct Raptor : Enemy {
|
||||
|
||||
enum {
|
||||
HIT_MASK = 0xFF7C00, // hands and head
|
||||
};
|
||||
|
||||
enum {
|
||||
ANIM_DEATH_1 = 9,
|
||||
ANIM_DEATH_2 = 10,
|
||||
};
|
||||
|
||||
enum {
|
||||
STATE_NONE = -1,
|
||||
STATE_DEATH,
|
||||
STATE_STOP,
|
||||
STATE_WALK,
|
||||
STATE_RUN,
|
||||
STATE_ATTACK_1,
|
||||
STATE_UNUSED,
|
||||
STATE_BAWL,
|
||||
STATE_ATTACK_2,
|
||||
STATE_BITE,
|
||||
};
|
||||
|
||||
Raptor(IGame *game, int entity) : Enemy(game, entity, 20, 341, 400.0f, 0.5f) {
|
||||
jointChest = -1;
|
||||
jointHead = 21;
|
||||
nextState = STATE_NONE;
|
||||
}
|
||||
|
||||
virtual int getStateGround() {
|
||||
if (!getEntity().flags.active)
|
||||
return state;
|
||||
|
||||
if (!think(true))
|
||||
return state;
|
||||
|
||||
if (nextState == state)
|
||||
nextState = STATE_NONE;
|
||||
|
||||
if (targetDead) {
|
||||
return (state == STATE_STOP || state == STATE_WALK) ? STATE_WALK : STATE_STOP;
|
||||
if (state != STATE_STOP) return STATE_STOP;
|
||||
return STATE_WALK;
|
||||
}
|
||||
|
||||
int mask = collide(target);
|
||||
|
||||
switch (state) {
|
||||
case STATE_STOP :
|
||||
if (nextState != STATE_NONE) return nextState;
|
||||
if ((mask & HIT_MASK) || (targetCanAttack && targetDist < RAPTOR_DIST_BITE)) return STATE_BITE;
|
||||
if (targetCanAttack && targetDist < RAPTOR_DIST_ATTACK) return STATE_ATTACK_1;
|
||||
if (mood == MOOD_SLEEP) return STATE_WALK;
|
||||
return STATE_RUN;
|
||||
case STATE_WALK :
|
||||
if (nextState != STATE_NONE) return STATE_STOP;
|
||||
if (mood != MOOD_SLEEP) return STATE_STOP;
|
||||
if (targetInView && randf() < 0.01f) {
|
||||
nextState = STATE_BAWL;
|
||||
return STATE_STOP;
|
||||
}
|
||||
break;
|
||||
case STATE_RUN :
|
||||
if (nextState != STATE_NONE) return STATE_STOP;
|
||||
if (mask & HIT_MASK) return STATE_STOP;
|
||||
if (targetCanAttack && targetDist < RAPTOR_DIST_ATTACK)
|
||||
return (randf() < 0.25) ? STATE_STOP : STATE_ATTACK_2;
|
||||
if (mood == MOOD_ESCAPE && targetInView) {
|
||||
nextState = STATE_BAWL;
|
||||
return STATE_STOP;
|
||||
}
|
||||
if (mood == MOOD_SLEEP)
|
||||
return STATE_STOP;
|
||||
break;
|
||||
case STATE_ATTACK_1 :
|
||||
case STATE_ATTACK_2 :
|
||||
case STATE_BITE :
|
||||
if (nextState == STATE_NONE && targetInView && (mask & HIT_MASK)) {
|
||||
bite(animation.getJoints(getMatrix(), jointHead, true).pos, 100);
|
||||
nextState = state == STATE_ATTACK_2 ? STATE_RUN : STATE_STOP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
virtual int getStateDeath() {
|
||||
if (state == STATE_DEATH) return state;
|
||||
return animation.setAnim((rand() % 2) ? ANIM_DEATH_1 : ANIM_DEATH_2);
|
||||
}
|
||||
|
||||
virtual void updatePosition() {
|
||||
if (state == STATE_DEATH) {
|
||||
animation.overrideMask = 0;
|
||||
angle.z = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float angleY = 0.0f;
|
||||
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||
|
||||
if (state == STATE_RUN || state == STATE_WALK)
|
||||
turn(angleY, state == STATE_RUN ? RAPTOR_TURN_FAST : RAPTOR_TURN_SLOW);
|
||||
|
||||
Enemy::updatePosition();
|
||||
setOverrides(true, jointChest, jointHead);
|
||||
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
13
src/format.h
13
src/format.h
@@ -602,11 +602,8 @@ namespace TR {
|
||||
|
||||
struct Entity {
|
||||
|
||||
typedef int16 Type;
|
||||
enum Type : int16 { NONE = -1, TR1_TYPES(DECL_ENUM) } type;
|
||||
|
||||
enum { NONE = -1, TR1_TYPES(DECL_ENUM) };
|
||||
|
||||
int16 type;
|
||||
int16 room;
|
||||
int32 x, y, z;
|
||||
angle rotation;
|
||||
@@ -624,6 +621,14 @@ namespace TR {
|
||||
return type >= ENEMY_TWIN && type <= ENEMY_LARSON;
|
||||
}
|
||||
|
||||
bool isBigEnemy() {
|
||||
return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR;
|
||||
}
|
||||
|
||||
bool isDoor() {
|
||||
return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT;
|
||||
}
|
||||
|
||||
int isItem() {
|
||||
return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) ||
|
||||
(type >= PUZZLE_1 && type <= PUZZLE_4) ||
|
||||
|
96
src/lara.h
96
src/lara.h
@@ -1135,6 +1135,16 @@ struct Lara : Character {
|
||||
|
||||
virtual void hit(int damage, Controller *enemy = NULL) {
|
||||
health -= damage;
|
||||
if (damage == 10000) { // T-Rex attack (fatal)
|
||||
pos = enemy->pos;
|
||||
angle = enemy->angle;
|
||||
|
||||
meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER);
|
||||
meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], 0);
|
||||
meshSwap(3, level->extra.weapons[Weapon::UZIS], 0);
|
||||
|
||||
animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1);
|
||||
}
|
||||
};
|
||||
|
||||
bool waterOut() {
|
||||
@@ -1864,8 +1874,6 @@ struct Lara : Character {
|
||||
}
|
||||
|
||||
virtual void update() {
|
||||
collisionOffset = vec3(0.0f);
|
||||
checkCollisions();
|
||||
Character::update();
|
||||
}
|
||||
|
||||
@@ -1998,7 +2006,9 @@ struct Lara : Character {
|
||||
vTilt *= rotFactor.y;
|
||||
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
|
||||
|
||||
if ((velocity + collisionOffset).length2() >= 1.0f)
|
||||
collisionOffset = vec3(0.0f);
|
||||
|
||||
if (checkCollisions() || (velocity + collisionOffset).length2() >= 1.0f)
|
||||
move();
|
||||
|
||||
if (getEntity().type != TR::Entity::LARA) {
|
||||
@@ -2019,7 +2029,7 @@ struct Lara : Character {
|
||||
return getEntity().type == TR::Entity::LARA ? pos : chestOffset;
|
||||
}
|
||||
|
||||
void checkCollisions() {
|
||||
bool checkCollisions() {
|
||||
// check static objects (TODO: check linked rooms?)
|
||||
TR::Room &room = getRoom();
|
||||
Box box(pos - vec3(LARA_RADIUS, LARA_HEIGHT, LARA_RADIUS), pos + vec3(LARA_RADIUS, 0.0f, LARA_RADIUS));
|
||||
@@ -2038,27 +2048,40 @@ struct Lara : Character {
|
||||
|
||||
if (!canHitAnim()) {
|
||||
hitDir = -1;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check enemies
|
||||
// check enemies & doors
|
||||
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;
|
||||
Controller *controller = (Controller*)e.controller;
|
||||
|
||||
vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - enemy->pos;
|
||||
vec3 p = dir.rotateY(-enemy->angle.y);
|
||||
if (e.isEnemy()) {
|
||||
if (e.type != TR::Entity::ENEMY_REX && (!e.flags.active || ((Character*)e.controller)->health <= 0)) continue;
|
||||
} else
|
||||
if (!e.isDoor()) continue;
|
||||
|
||||
Box enemyBox = enemy->getBoundingBoxLocal();
|
||||
if (!enemyBox.contains(p))
|
||||
vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - controller->pos;
|
||||
vec3 p = dir.rotateY(controller->angle.y);
|
||||
|
||||
Box box = controller->getBoundingBoxLocal();
|
||||
if (!box.contains(p))
|
||||
continue;
|
||||
|
||||
// get shift
|
||||
p += enemyBox.pushOut2D(p);
|
||||
p = (p.rotateY(enemy->angle.y) + enemy->pos) - pos;
|
||||
collisionOffset += vec3(p.x, 0.0f, p.z);
|
||||
if (e.isEnemy()) { // enemy collision
|
||||
if (!collide(controller, false))
|
||||
continue;
|
||||
velocity.x = velocity.y = 0.0f;
|
||||
} else { // door collision
|
||||
p += box.pushOut2D(p);
|
||||
p = (p.rotateY(-controller->angle.y) + controller->pos) - pos;
|
||||
collisionOffset += vec3(p.x, 0.0f, p.z);
|
||||
}
|
||||
|
||||
if (e.type == TR::Entity::ENEMY_REX && ((Character*)e.controller)->health <= 0)
|
||||
return true;
|
||||
if (e.isDoor())
|
||||
return true;
|
||||
|
||||
// get hit dir
|
||||
if (hitDir == -1) {
|
||||
@@ -2068,10 +2091,11 @@ struct Lara : Character {
|
||||
}
|
||||
|
||||
hitDir = angleQuadrant(dir.rotateY(angle.y + PI * 0.5f).angleY());
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
hitDir = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
void move() {
|
||||
@@ -2126,42 +2150,6 @@ struct Lara : Character {
|
||||
pos = opos;
|
||||
}
|
||||
|
||||
/*
|
||||
TR::Animation *anim = animation;
|
||||
Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox();
|
||||
// check static meshes in the room
|
||||
if (canPassGap) {
|
||||
TR::Room &r = level->rooms[e.room];
|
||||
for (int i = 0; i < r.meshesCount; i++) {
|
||||
TR::Room::Mesh &m = r.meshes[i];
|
||||
TR::StaticMesh *sm = level->getMeshByID(m.meshID);
|
||||
if (sm->flags != 2) continue; // no have collision box
|
||||
|
||||
Box mBox;
|
||||
vec3 offset(m.x, m.y, m.z);
|
||||
sm->getBox(true, m.rotation, mBox);
|
||||
mBox.min += offset;
|
||||
mBox.max += offset;
|
||||
|
||||
if (eBox.intersect(mBox)) {
|
||||
canPassGap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check entities in the room
|
||||
if (canPassGap)
|
||||
for (int i = 0; i < level->entitiesCount; i++)
|
||||
if (i != entity && level->entities[i].room == e.room && level->entities[i].controller) {
|
||||
Box mBox = ((Controller*)level->entities[i].controller)->getBoundingBox();
|
||||
if (eBox.intersect(mBox)) {
|
||||
canPassGap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// get current leading foot in animation
|
||||
int rightStart = 0;
|
||||
if (state == STATE_RUN) rightStart = 6;
|
||||
|
41
src/level.h
41
src/level.h
@@ -57,8 +57,8 @@ struct Level : IGame {
|
||||
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 uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
|
||||
return zoneCache->findPath(ascend, descend, big, boxStart, boxEnd, zones, boxes);
|
||||
}
|
||||
|
||||
virtual void setClipParams(float clipSign, float clipHeight) {
|
||||
@@ -107,6 +107,10 @@ struct Level : IGame {
|
||||
Core::pass = Core::passCompose;
|
||||
renderScene(roomIndex);
|
||||
}
|
||||
|
||||
virtual void fxQuake(float time) {
|
||||
camera->shake = time;
|
||||
}
|
||||
//==============================
|
||||
|
||||
Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), lara(NULL) {
|
||||
@@ -137,24 +141,28 @@ struct Level : IGame {
|
||||
case TR::Entity::ENEMY_BAT :
|
||||
entity.controller = new Bat(this, i);
|
||||
break;
|
||||
case TR::Entity::ENEMY_TWIN :
|
||||
case TR::Entity::ENEMY_CROCODILE_LAND :
|
||||
case TR::Entity::ENEMY_CROCODILE_WATER :
|
||||
case TR::Entity::ENEMY_LION_MALE :
|
||||
case TR::Entity::ENEMY_LION_FEMALE :
|
||||
case TR::Entity::ENEMY_PUMA :
|
||||
case TR::Entity::ENEMY_GORILLA :
|
||||
case TR::Entity::ENEMY_RAT_LAND :
|
||||
case TR::Entity::ENEMY_RAT_WATER :
|
||||
case TR::Entity::ENEMY_REX :
|
||||
case TR::Entity::ENEMY_RAPTOR :
|
||||
case TR::Entity::ENEMY_TWIN :
|
||||
case TR::Entity::ENEMY_CROCODILE_LAND :
|
||||
case TR::Entity::ENEMY_CROCODILE_WATER :
|
||||
case TR::Entity::ENEMY_LION_MALE :
|
||||
case TR::Entity::ENEMY_LION_FEMALE :
|
||||
case TR::Entity::ENEMY_PUMA :
|
||||
case TR::Entity::ENEMY_GORILLA :
|
||||
case TR::Entity::ENEMY_RAT_LAND :
|
||||
case TR::Entity::ENEMY_RAT_WATER :
|
||||
case TR::Entity::ENEMY_REX :
|
||||
entity.controller = new Rex(this, i);
|
||||
break;
|
||||
case TR::Entity::ENEMY_RAPTOR :
|
||||
entity.controller = new Raptor(this, i);
|
||||
break;
|
||||
case TR::Entity::ENEMY_MUTANT_1 :
|
||||
case TR::Entity::ENEMY_MUTANT_2 :
|
||||
case TR::Entity::ENEMY_MUTANT_3 :
|
||||
case TR::Entity::ENEMY_CENTAUR :
|
||||
case TR::Entity::ENEMY_MUMMY :
|
||||
case TR::Entity::ENEMY_CENTAUR :
|
||||
case TR::Entity::ENEMY_MUMMY :
|
||||
case TR::Entity::ENEMY_LARSON :
|
||||
entity.controller = new Enemy(this, i, 100, 10, 0.0f);
|
||||
entity.controller = new Enemy(this, i, 100, 10, 0.0f, 0.0f);
|
||||
break;
|
||||
case TR::Entity::DOOR_1 :
|
||||
case TR::Entity::DOOR_2 :
|
||||
@@ -205,7 +213,6 @@ struct Level : IGame {
|
||||
case TR::Entity::MOVING_BLOCK :
|
||||
entity.controller = new MovingBlock(this, i);
|
||||
break;
|
||||
case 1592 :
|
||||
case TR::Entity::FALLING_CEILING_1 :
|
||||
case TR::Entity::FALLING_CEILING_2 :
|
||||
case TR::Entity::FALLING_SWORD :
|
||||
|
Reference in New Issue
Block a user