scummvm/engines/hdb/ai-waypoint.cpp
2021-12-26 18:48:43 +01:00

464 lines
12 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "hdb/hdb.h"
#include "hdb/ai.h"
#include "hdb/gfx.h"
#include "hdb/map.h"
#include "hdb/sound.h"
namespace HDB {
void AI::addWaypoint(int px, int py, int x, int y, int level) {
// at the max yet?
if (_numWaypoints >= kMaxWaypoints || (playerOnIce() && _player->goalX))
return;
// first, let's see if this is a duplicate waypoint
int i;
for (i = 0; i < _numWaypoints; i++)
if (_waypoints[i].x == x && _waypoints[i].y == y)
return;
// check to make sure the path is clear...
// if it's not, don't add to waypoint list!
// The destination x,y might be modified, so
// we'll pass in the address to them...
int nx = x;
int ny = y;
if (!_numWaypoints) {
// if player is already moving and this is the first waypoint, forget it.
// player is using the keyboard and must be fully stopped before laying
// the first waypoint
if (_player->goalX)
return;
// trace from player to new spot
// return value of 0 only means a diagonal was selected!
if (!traceStraightPath(px, py, &nx, &ny, &level)) {
int tx, ty, tx2, ty2;
// it didn't work, so player is probably trying some diagonal movement.
// let's break it down into two waypoints: one horz, one vert
tx = nx;
ty = py;
tx2 = nx;
ty2 = ny;
int lvl1, lvl2;
lvl1 = lvl2 = level;
if (traceStraightPath(px, py, &tx, &ty, &lvl1)) {
if (tx != nx || ty != py)
goto newpath;
traceStraightPath(tx, ty, &tx2, &ty2, &lvl2);
if (tx2 != nx || ty2 != ny)
goto newpath;
_waypoints[_numWaypoints].x = tx;
_waypoints[_numWaypoints].y = ty;
_waypoints[_numWaypoints].level = lvl1;
_numWaypoints++;
_waypoints[_numWaypoints].x = nx;
_waypoints[_numWaypoints].y = ny;
_waypoints[_numWaypoints].level = lvl2;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
if (onEvenTile(_player->x, _player->y))
setEntityGoal(_player, tx, ty);
} else {
newpath:
tx = px;
ty = ny;
tx2 = nx;
ty2 = ny;
lvl1 = lvl2 = level;
if (traceStraightPath(px, py, &tx, &ty, &lvl1)) {
if (tx != px || ty != ny)
return;
traceStraightPath(tx, ty, &nx, &ny, &lvl2);
if (tx2 != nx || ty2 != ny)
return;
_waypoints[_numWaypoints].x = tx;
_waypoints[_numWaypoints].y = ty;
_waypoints[_numWaypoints].level = lvl1;
_numWaypoints++;
_waypoints[_numWaypoints].x = nx;
_waypoints[_numWaypoints].y = ny;
_waypoints[_numWaypoints].level = lvl2;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
if (onEvenTile(_player->x, _player->y))
setEntityGoal(_player, tx, ty);
}
}
return;
}
// create a waypoint @ the player x,y? NO!
if ((nx != px || ny != py) && onEvenTile(_player->x, _player->y))
setEntityGoal(_player, nx, ny);
else
return;
} else {
// trace from last waypoint to new spot
level = _waypoints[_numWaypoints - 1].level;
if (!traceStraightPath(_waypoints[_numWaypoints - 1].x, _waypoints[_numWaypoints - 1].y, &nx, &ny, &level)) {
int tx, ty, tx2, ty2;
// it didn't work, so player is probably trying some diagonal movement.
// let's break it down into two waypoints: one horz, one vert
px = _waypoints[_numWaypoints - 1].x;
py = _waypoints[_numWaypoints - 1].y;
level = _waypoints[_numWaypoints - 1].level;
tx = nx;
ty = py;
tx2 = nx;
ty2 = ny;
int lvl1, lvl2;
lvl1 = lvl2 = level;
if (traceStraightPath(px, py, &tx, &ty, &lvl1)) {
if (tx != nx || ty != py)
goto newpath2;
traceStraightPath(tx, ty, &tx2, &ty2, &lvl2);
if (tx2 != nx || ty2 != ny)
goto newpath2;
if (_numWaypoints < kMaxWaypoints) {
_waypoints[_numWaypoints].x = tx;
_waypoints[_numWaypoints].y = ty;
_waypoints[_numWaypoints].level = lvl1;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
}
if (_numWaypoints < kMaxWaypoints) {
_waypoints[_numWaypoints].x = nx;
_waypoints[_numWaypoints].y = ny;
_waypoints[_numWaypoints].level = lvl2;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
}
} else {
newpath2:
tx = px;
ty = ny;
tx2 = nx;
ty2 = ny;
lvl1 = lvl2 = level;
if (traceStraightPath(px, py, &tx, &ty, &lvl1)) {
if (tx != px || ty != ny)
return;
traceStraightPath(tx, ty, &nx, &ny, &lvl2);
if (tx2 != nx || ty2 != ny)
return;
if (_numWaypoints < kMaxWaypoints) {
_waypoints[_numWaypoints].x = tx;
_waypoints[_numWaypoints].y = ty;
_waypoints[_numWaypoints].level = lvl1;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
}
if (_numWaypoints < kMaxWaypoints) {
_waypoints[_numWaypoints].x = nx;
_waypoints[_numWaypoints].y = ny;
_waypoints[_numWaypoints].level = lvl2;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
}
}
}
return;
}
// create a waypoint @ the player x,y? NO!
if (nx == px && ny == py)
return;
// make sure potential waypoint isn't on other waypoints!
for (i = 0; i < _numWaypoints; i++)
if (_waypoints[i].x == nx && _waypoints[i].y == ny)
return;
}
if (_numWaypoints < kMaxWaypoints) {
_waypoints[_numWaypoints].x = nx;
_waypoints[_numWaypoints].y = ny;
_waypoints[_numWaypoints].level = level;
_numWaypoints++;
g_hdb->_sound->playSound(SND_MENU_SLIDER);
}
}
void AI::removeFirstWaypoint() {
if (!_numWaypoints)
return;
int i;
for (i = 0; i < _numWaypoints; i++) {
_waypoints[i] = _waypoints[i + 1];
}
_waypoints[i].x = 0;
_waypoints[i].y = 0;
_waypoints[i].level = 0;
_numWaypoints--;
}
void AI::clearWaypoints() {
for (uint8 i = 0; i < ARRAYSIZE(_waypoints); i++) {
_waypoints[i].reset();
}
_numWaypoints = 0;
}
bool AI::traceStraightPath(int x1, int y1, int *x2, int *y2, int *level) {
// this checks to make sure we're only going vert or horz
if (x1 != *x2 && y1 != *y2)
return false;
// this sets a -1, 0, or 1 step value
int xVel = *x2 - x1;
if (xVel < 0)
xVel = -1;
if (xVel > 0)
xVel = 1;
int yVel = *y2 - y1;
if (yVel < 0)
yVel = -1;
if (yVel > 0)
yVel = 1;
AIEntity *e;
while (1) {
// clear tile ahead?
bool entOK = false;
bool ok = false;
uint32 flags = g_hdb->_map->getMapBGTileFlags(x1, y1);
if (flags & kFlagStairTop)
*level = 2;
else if (flags & kFlagStairBot)
*level = 1;
// Floor level 1
if (*level < 2) {
ok = !(flags & (kFlagPlayerBlock | kFlagMonsterBlock));
// if it's blocking, is it rad or plasma? (might be melted stuff on it)
if (!ok) {
ok = ((flags & kFlagPlasmaFloor) == kFlagPlasmaFloor) +
((flags & kFlagRadFloor) == kFlagRadFloor);
e = findEntity(x1, y1);
if (e && g_hdb->_ai->walkThroughEnt(e->type))
entOK = true;
else if (ok && e && (e->state == STATE_FLOATING || e->state == STATE_MELTED || e == _player))
entOK = ok = true;
else
ok = false;
} else if (ok &&
((flags & kFlagWater) == kFlagWater ||
(flags & kFlagSlime) == kFlagSlime)) {
// if it's non-blocking, is there water or slime?
e = findEntity(x1, y1);
if (e && g_hdb->_ai->walkThroughEnt(e->type))
entOK = true;
else
if (e && (e->state == STATE_FLOATING || e->state == STATE_MELTED || e == _player))
entOK = ok = true;
else
ok = false;
}
} else {
// Floor level 2
if (g_hdb->_map->getMapFGTileIndex(x1, y1) >= 0) // is there a foregnd tile? its flags take precedence on Level 2
ok = !(g_hdb->_map->getMapFGTileFlags(x1, y1) & (kFlagPlayerBlock | kFlagMonsterBlock));
else {
flags = g_hdb->_map->getMapBGTileFlags(x1, y1);
ok = !(flags & (kFlagPlayerBlock | kFlagMonsterBlock));
// if it's blocking, is it rad or plasma? (might be melted stuff on it)
if (!ok) {
ok = ((flags & kFlagPlasmaFloor) == kFlagPlasmaFloor) +
((flags & kFlagRadFloor) == kFlagRadFloor);
e = findEntity(x1, y1);
if (e && g_hdb->_ai->walkThroughEnt(e->type))
entOK = true;
else if (ok && e && (e->state == STATE_FLOATING || e->state == STATE_MELTED || e == _player))
entOK = ok = true;
else
ok = false;
} else if (ok &&
((flags & kFlagWater) == kFlagWater ||
(flags & kFlagSlime) == kFlagSlime)) {
// if it's non-blocking, is there water or slime?
e = findEntity(x1, y1);
if (e && g_hdb->_ai->walkThroughEnt(e->type))
entOK = true;
else
if (e && (e->state == STATE_FLOATING || e->state == STATE_MELTED || e == _player))
entOK = ok = true;
else
ok = false;
}
}
}
if (ok) {
e = findEntity(x1, y1);
if (e == _player)
e = nullptr;
else if (g_hdb->_map->laserBeamExist(x1, y1)) {
*x2 = x1 - xVel;
*y2 = y1 - yVel;
return true;
} else if (e && (e->level != _player->level) && (g_hdb->_map->getMapFGTileFlags(e->tileX, e->tileY) & kFlagGrating)) {
// on the same level????
entOK = 1;
}
if (e && !entOK) {
if (g_hdb->_ai->walkThroughEnt(e->type)) {
// yes! are we at desired location?
if (x1 == *x2 && y1 == *y2)
return true;
} else {
// solid tile! back up one and return!
*x2 = x1 - xVel;
*y2 = y1 - yVel;
return true;
}
} else if (x1 == *x2 && y1 == *y2) {
// yes! are we at desired location?
return true;
}
} else {
// solid tile! back up one and return!
*x2 = x1 - xVel;
*y2 = y1 - yVel;
return true;
}
x1 += xVel;
y1 += yVel;
}
return true;
}
Tile *AI::getStandFrameDir(AIEntity *e) {
switch (e->dir) {
case DIR_DOWN:
if (e->standdownFrames)
return e->standdownGfx[0];
else if (e->movedownFrames)
return e->movedownGfx[0];
break;
case DIR_UP:
if (e->standupFrames)
return e->standupGfx[0];
else if (e->moveupFrames)
return e->moveupGfx[0];
break;
case DIR_LEFT:
if (e->standleftFrames)
return e->standleftGfx[0];
else if (e->moveleftFrames)
return e->moveleftGfx[0];
break;
case DIR_RIGHT:
if (e->standrightFrames)
return e->standrightGfx[0];
else if (e->moverightFrames)
return e->moverightGfx[0];
break;
case DIR_NONE:
default:
break;
}
return e->standdownGfx[0];
}
void AI::drawWayPoints() {
static int anim = 0;
static uint32 delay = g_hdb->getTimeSlice();
static int alpha = 255;
static int aVel = -4;
int mapX, mapY;
g_hdb->_map->getMapXY(&mapX, &mapY);
for (int i = 0; i < _numWaypoints; i++) {
int x = _waypoints[i].x * kTileWidth;
int y = _waypoints[i].y * kTileHeight;
if (x > mapX - 32 && (x < (mapX + g_hdb->_screenWidth)) &&
y > mapY - 32 && (y < (mapY + g_hdb->_screenHeight)))
_waypointGfx[anim]->drawMasked(x - mapX, y - mapY, alpha);
}
// vary the alpha blending
alpha = (alpha + aVel);
if (alpha < 64) {
alpha = 64;
aVel = -aVel;
}
if (alpha > 200) {
alpha = 200;
aVel = -aVel;
}
// don't animate every single game frame...
if (delay > g_hdb->getTimeSlice())
return;
delay = g_hdb->getTimeSlice() + 100;
// cycle the waypoint gfx animation
anim++;
if (anim == 4)
anim = 0;
}
} // End of Namespace