citylimits/core/sprite.cpp

2040 lines
49 KiB
C++
Raw Normal View History

2024-05-03 22:50:34 -04:00
/* sprite.cpp
*
* Micropolis, Unix Version. This game was released for the Unix platform
* in or about 1990 and has been modified for inclusion in the One Laptop
* Per Child program. Copyright (C) 1989 - 2007 Electronic Arts Inc. If
* you need assistance with this program, you may contact:
* http://wiki.laptop.org/go/Micropolis or email micropolis@laptop.org.
*
* 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/>.
*
* ADDITIONAL TERMS per GNU GPL Section 7
*
* No trademark or publicity rights are granted. This license does NOT
* give you any right, title or interest in the trademark SimCity or any
* other Electronic Arts trademark. You may not distribute any
* modification of this program using the trademark SimCity or claim any
* affliation or association with Electronic Arts Inc. or its employees.
*
* Any propagation or conveyance of this program must include this
* copyright notice and these terms.
*
* If you convey this program (or any modifications of it) and assume
* contractual liability for the program to recipients of it, you agree
* to indemnify Electronic Arts for any liability that those contractual
* assumptions impose on Electronic Arts.
*
* You may not misrepresent the origins of this program; modified
* versions of the program must be marked as such and not identified as
* the original program.
*
* This disclaimer supplements the one included in the General Public
* License. TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
* PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
* OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK. THE ENTIRE RISK OF
* SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU. ELECTRONIC ARTS
* DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
* INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
* FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
* RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
* USAGE, OR TRADE PRACTICE. ELECTRONIC ARTS DOES NOT WARRANT AGAINST
* INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
* MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
* UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
* WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
* CORRECTED. NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
* ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME
* JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
* WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
* CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
* NOT APPLY TO YOU.
*/
/**
* @file sprite.cpp
* @brief Manages sprite objects and their behavior in Micropolis.
*
* This file defines functions related to the creation, movement,
* interaction, and destruction of various sprites such as trains,
* helicopters, airplanes, ships, monsters, tornadoes, and explosions
* within the Micropolis game. It handles the logic for sprite
* behavior, including movement patterns, interactions with other
* sprites and the game map, and event-triggered actions like
* explosions and sound effects.
*/
////////////////////////////////////////////////////////////////////////
#include "micropolis.h"
#include "text.h"
////////////////////////////////////////////////////////////////////////
#define TRA_GROOVE_X -39
#define TRA_GROOVE_Y 6
#define BUS_GROOVE_X -39
#define BUS_GROOVE_Y 6
////////////////////////////////////////////////////////////////////////
/**
* Create and initialize a sprite.
* @param name Name of the sprite (always \c "").
* @param type Type pf the sprite. @see SpriteType.
* @param x X coordinate of the sprite (in pixels).
* @param y Y coordinate of the sprite (in pixels).
* @return New sprite object.
*/
SimSprite *Micropolis::newSprite(const std::string &name, int type, int x, int y)
{
SimSprite *sprite;
// If a sprite is available at the pool, use one.
// else, allocate a new one.
if (freeSprites) {
sprite = freeSprites;
freeSprites = sprite->next;
} else {
sprite = (SimSprite *)newPtr(sizeof (SimSprite));
}
sprite->name = name;
sprite->type = type;
initSprite(sprite, x, y);
sprite->next = spriteList;
spriteList = sprite;
return sprite;
}
/** Re-initialize an existing sprite.
* @param sprite Sprite to re-use.
* @param x New x coordinate of the sprite (in pixels?).
* @param y New y coordinate of the sprite (in pixels?).
* @todo Make derived classes for each type.
* @todo Move code to (derived) #SimSprite methods.
*/
void Micropolis::initSprite(SimSprite *sprite, int x, int y)
{
sprite->x = x;
sprite->y = y;
sprite->frame = 0;
sprite->origX = 0;
sprite->origY = 0;
sprite->destX = 0;
sprite->destY = 0;
sprite->count = 0;
sprite->soundCount = 0;
sprite->dir = 0;
sprite->newDir = 0;
sprite->step = 0;
sprite->flag = 0;
sprite->control = -1;
sprite->turn = 0;
sprite->accel = 0;
sprite->speed = 100;
if (globalSprites[sprite->type] == NULL) {
globalSprites[sprite->type] = sprite;
}
switch (sprite->type) {
case SPRITE_TRAIN:
sprite->width = 32;
sprite->height = 32;
sprite->xOffset = 32;
sprite->yOffset = -16;
sprite->xHot = 40;
sprite->yHot = -8;
sprite->frame = 1;
sprite->dir = 4;
break;
case SPRITE_SHIP:
sprite->width = 48;
sprite->height = 48;
sprite->xOffset = 32;
sprite->yOffset = -16;
sprite->xHot = 48;
sprite->yHot = 0;
if (x < (4 <<4)) {
sprite->frame = 3;
} else if (x >= ((WORLD_W - 4) <<4)) {
sprite->frame = 7;
} else if (y < (4 <<4)) {
sprite->frame = 5;
} else if (y >= ((WORLD_H - 4) <<4)) {
sprite->frame = 1;
} else {
sprite->frame = 3;
}
sprite->newDir = sprite->frame;
sprite->dir = 10;
sprite->count = 1;
break;
case SPRITE_MONSTER:
sprite->width = 48;
sprite->height = 48;
sprite->xOffset = 24;
sprite->yOffset = 0;
sprite->xHot = 40;
sprite->yHot = 16;
if (x > ((WORLD_W <<4) / 2)) {
if (y > ((WORLD_H <<4) / 2)) {
sprite->frame = 10;
} else {
sprite->frame = 7;
}
} else if (y > ((WORLD_H <<4) / 2)) {
sprite->frame = 1;
} else {
sprite->frame = 4;
}
sprite->count = 1000;
sprite->destX = pollutionMaxX <<4;
sprite->destY = pollutionMaxY <<4;
sprite->origX = sprite->x;
sprite->origY = sprite->y;
break;
case SPRITE_HELICOPTER:
sprite->width = 32;
sprite->height = 32;
sprite->xOffset = 32;
sprite->yOffset = -16;
sprite->xHot = 40;
sprite->yHot = -8;
sprite->frame = 5;
sprite->count = 1500;
sprite->destX = getRandom((WORLD_W <<4) - 1);
sprite->destY = getRandom((WORLD_H <<4) - 1);
sprite->origX = x - 30;
sprite->origY = y;
break;
case SPRITE_AIRPLANE:
sprite->width = 48;
sprite->height = 48;
sprite->xOffset = 24;
sprite->yOffset = 0;
sprite->xHot = 48;
sprite->yHot = 16;
if (x > ((WORLD_W - 20) <<4)) {
sprite->x -= 100 + 48;
sprite->destX = sprite->x - 200;
sprite->frame = 7;
} else {
sprite->destX = sprite->x + 200;
sprite->frame = 11;
}
sprite->destY = sprite->y;
break;
case SPRITE_TORNADO:
sprite->width = 48;
sprite->height = 48;
sprite->xOffset = 24;
sprite->yOffset = 0;
sprite->xHot = 40;
sprite->yHot = 36;
sprite->frame = 1;
sprite->count = 200;
break;
case SPRITE_EXPLOSION:
sprite->width = 48;
sprite->height = 48;
sprite->xOffset = 24;
sprite->yOffset = 0;
sprite->xHot = 40;
sprite->yHot = 16;
sprite->frame = 1;
break;
case SPRITE_BUS:
sprite->width = 32;
sprite->height = 32;
sprite->xOffset = 30;
sprite->yOffset = -18;
sprite->xHot = 40;
sprite->yHot = -8;
sprite->frame = 1;
sprite->dir = 1;
break;
}
}
/**
* Destroy all sprites by de-activating them all (setting their
* SimSprite::frame to 0).
*/
void Micropolis::destroyAllSprites()
{
SimSprite *sprite;
for (sprite = spriteList; sprite != NULL; sprite = sprite->next) {
sprite->frame = 0;
}
}
/**
* Destroy the sprite by taking it out of the active list.
* @param sprite Sprite to destroy.
* @todo Break the connection between any views that are following this sprite.
*/
void Micropolis::destroySprite(SimSprite *sprite)
{
SimSprite **sp;
if (globalSprites[sprite->type] == sprite) {
globalSprites[sprite->type] = (SimSprite *)NULL;
}
for (sp = &spriteList; *sp != NULL; sp = &((*sp)->next)) {
if (sprite == (*sp)) {
*sp = sprite->next;
break;
}
}
sprite->next = freeSprites;
freeSprites = sprite;
}
/**
* Return the sprite of the give type, if available.
* @param type Type of the sprite.
* @return Pointer to the active sprite if avaiable, else \c NULL.
*/
SimSprite *Micropolis::getSprite(int type)
{
SimSprite *sprite = globalSprites[type];
if (sprite == NULL || sprite->frame == 0) {
return (SimSprite *)NULL;
} else {
return sprite;
}
}
/**
* Make a sprite either by re-using the old one, or by making a new one.
* @param type Sprite type of the new sprite.
* @param x X coordinate of the new sprite.
* @param y Y coordinate of the new sprite.
*/
SimSprite *Micropolis::makeSprite(int type, int x, int y)
{
SimSprite *sprite;
sprite = globalSprites[type];
if (sprite == NULL) {
sprite = newSprite("", type, x, y);
} else {
initSprite(sprite, x, y);
}
return sprite;
}
/**
* Get character from the map.
* @param x X coordinate in pixels.
* @param y Y coordinate in pixels.
* @return Map character if on-map, or \c -1 if off-map.
*/
short Micropolis::getChar(int x, int y)
{
// Convert sprite coordinates to tile coordinates.
x >>= 4;
y >>= 4;
if (!testBounds(x, y)) {
return -1;
} else {
return map[x][y] & LOMASK;
}
}
/**
* Turn.
* @param p Present direction (1..8).
* @param d Destination direction (1..8).
* @return New direction.
* @todo Remove local magic constants and document the code.
*/
short Micropolis::turnTo(int p, int d)
{
if (p == d) {
return p;
}
if (p < d) {
if (d - p < 4) {
p++;
} else {
p--;
}
} else {
if (p - d < 4) {
p--;
} else {
p++;
}
}
if (p > 8) {
p = 1;
}
if (p < 1) {
p = 8;
}
return p;
}
/** ???
* @todo Figure out what this function is doing.
* @todo Remove local magic constants and document the code.
*/
bool Micropolis::tryOther(int Tpoo, int Told, int Tnew)
{
short z;
z = Told + 4;
if (z > 8) {
z -= 8;
}
if (Tnew != z) {
return false;
}
if (Tpoo == POWERBASE || Tpoo == POWERBASE + 1
|| Tpoo == RAILBASE || Tpoo == RAILBASE + 1) {
return true;
}
return false;
}
/**
* Check whether a sprite is still entirely on-map.
* @param sprite Sprite to check.
* @return Sprite is at least partly off-map.
*/
bool Micropolis::spriteNotInBounds(SimSprite *sprite)
{
int x = sprite->x + sprite->xHot;
int y = sprite->y + sprite->yHot;
return x < 0 || y < 0 || x >= (WORLD_W <<4) || y >= (WORLD_H <<4);
}
/**
* Get direction (0..8?) to get from starting point to destination point.
* @param orgX X coordinate starting point.
* @param orgY Y coordinate starting point.
* @param desX X coordinate destination point.
* @param desY Y coordinate destination point.
* @return Direction to go in.
* @todo Remove local magic constants and document the code.
* @bug Has a condition that never holds.
*/
short Micropolis::getDir(int orgX, int orgY, int desX, int desY)
{
static const short Gdtab[13] = { 0, 3, 2, 1, 3, 4, 5, 7, 6, 5, 7, 8, 1 };
int dispX, dispY, z;
dispX = desX - orgX;
dispY = desY - orgY;
if (dispX < 0) {
if (dispY < 0) {
z = 11;
} else {
z = 8;
}
} else {
if (dispY < 0) {
z = 2;
} else {
z = 5;
}
}
dispX = absoluteValue(dispX);
dispY = absoluteValue(dispY);
absDist = dispX + dispY;
if (dispX * 2 < dispY) {
z++;
} else if (dispY * 2 < dispY) { // XXX This never holds!!
z--;
}
if (z < 0 || z > 12) {
z = 0;
}
return Gdtab[z];
}
/**
* Compute Manhattan distance between two points.
* @param x1 X coordinate first point.
* @param y1 Y coordinate first point.
* @param x2 X coordinate second point.
* @param y2 Y coordinate second point.
* @return Manhattan distance between both points.
*/
int Micropolis::getDistance(int x1, int y1, int x2, int y2)
{
return absoluteValue(x1 - x2) + absoluteValue(y1 - y2);
}
/**
* Check whether two sprites collide with each other.
* @param s1 First sprite.
* @param s2 Second sprite.
* @return Sprites are colliding.
*/
bool Micropolis::checkSpriteCollision(SimSprite *s1, SimSprite *s2)
{
return s1->frame != 0 && s2->frame != 0 &&
getDistance(s1->x + s1->xHot, s1->y + s1->yHot,
s2->x + s2->xHot, s2->y + s2->yHot) < 30;
}
/**
* Move all sprites.
*
* Sprites with SimSprite::frame == 0 are removed.
* @todo It uses SimSprite::name[0] == '\0' as condition which seems stupid.
* @todo Micropolis::destroySprite modifies the Micropolis::spriteList
* while we loop over it.
*/
void Micropolis::moveObjects()
{
SimSprite *sprite;
if (!simSpeed) {
return;
}
spriteCycle++;
for (sprite = spriteList; sprite != NULL;) {
if (sprite->frame > 0) {
switch (sprite->type) {
case SPRITE_TRAIN:
doTrainSprite(sprite);
break;
case SPRITE_HELICOPTER:
doCopterSprite(sprite);
break;
case SPRITE_AIRPLANE:
doAirplaneSprite(sprite);
break;
case SPRITE_SHIP:
doShipSprite(sprite);
break;
case SPRITE_MONSTER:
doMonsterSprite(sprite);
break;
case SPRITE_TORNADO:
doTornadoSprite(sprite);
break;
case SPRITE_EXPLOSION:
doExplosionSprite(sprite);
break;
case SPRITE_BUS:
doBusSprite(sprite);
break;
}
sprite = sprite->next;
} else {
if (sprite->name[0] == '\0') {
SimSprite *s = sprite;
sprite = sprite->next;
destroySprite(s);
} else {
sprite = sprite->next;
}
}
}
}
/**
* Move train sprite.
* @param sprite Train sprite.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doTrainSprite(SimSprite *sprite)
{
/* Offset in pixels of sprite x and y to map tile */
static const short Cx[4] = { 0, 16, 0, -16 };
static const short Cy[4] = { -16, 0, 16, 0 };
/* X and Y movement of the sprite in pixels */
static const short Dx[5] = { 0, 4, 0, -4, 0 };
static const short Dy[5] = { -4, 0, 4, 0, 0 };
static const short TrainPic2[5] = { 1, 2, 1, 2, 5 };
short z, dir, dir2;
short c;
if (sprite->frame == 3 || sprite->frame == 4) {
sprite->frame = TrainPic2[sprite->dir];
}
sprite->x += Dx[sprite->dir];
sprite->y += Dy[sprite->dir];
if ((spriteCycle & 3) == 0) {
dir = getRandom16() & 3;
for (z = dir; z < dir + 4; z++) {
dir2 = z & 3;
if (sprite->dir != 4) {
if (dir2 == ((sprite->dir + 2) & 3)) {
continue;
}
}
c = getChar(sprite->x + Cx[dir2] + 48, sprite->y + Cy[dir2]);
if ((c >= RAILBASE && c <= LASTRAIL) /* track? */
|| c == RAILVPOWERH || c == RAILHPOWERV) {
if (sprite->dir != dir2 && sprite->dir != 4) {
if (sprite->dir + dir2 == 3) {
sprite->frame = 3;
} else {
sprite->frame = 4;
}
} else {
sprite->frame = TrainPic2[dir2];
}
if (c == HRAIL || c == VRAIL) {
sprite->frame = 5;
}
sprite->dir = dir2;
return;
}
}
if (sprite->dir == 4) {
sprite->frame = 0;
return;
}
sprite->dir = 4;
}
}
/**
* Move helicopter sprite.
* @param sprite Helicopter sprite.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doCopterSprite(
SimSprite *sprite)
{
static const short CDx[9] = { 0, 0, 3, 5, 3, 0, -3, -5, -3 };
static const short CDy[9] = { 0, -5, -3, 0, 3, 5, 3, 0, -3 };
if (sprite->soundCount > 0) {
sprite->soundCount--;
}
if (sprite->control < 0) {
if (sprite->count > 0) {
sprite->count--;
}
if (sprite->count == 0) {
/* Attract copter to monster so it blows up more often */
SimSprite *s = getSprite(SPRITE_MONSTER);
if (s != NULL) {
sprite->destX = s->x;
sprite->destY = s->y;
} else {
/* Attract copter to tornado so it blows up more often */
s = getSprite(SPRITE_TORNADO);
if (s != NULL) {
sprite->destX = s->x;
sprite->destY = s->y;
} else {
sprite->destX = sprite->origX;
sprite->destY = sprite->origY;
}
}
}
if (sprite->count == 0) { /* land */
getDir(sprite->x, sprite->y, sprite->origX, sprite->origY);
if (absDist < 30) {
sprite->frame = 0;
return;
}
}
} else {
getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
if (absDist < 16) {
sprite->destX = sprite->origX;
sprite->destY = sprite->origY;
sprite->control = -1;
}
}
if (sprite->soundCount == 0) { /* send report */
// Convert sprite coordinates to world coordinates.
short x = (sprite->x + 48) / 16;
short y = sprite->y / 16;
if (x >= 0 && x < WORLD_W && y >= 0 && y < WORLD_H) {
/* Don changed from 160 to 170 to shut the #$%#$% thing up! */
int chopperX = x + 1;
int chopperY = y + 1;
if (trafficDensityMap.worldGet(x, y) > 170 && (getRandom16() & 7) == 0) {
sendMessage(MESSAGE_HEAVY_TRAFFIC, chopperX, chopperY, true);
makeSound("city", "HeavyTraffic", chopperX, chopperY); /* chopper */
sprite->soundCount = 200;
}
}
}
short z = sprite->frame;
if ((spriteCycle & 3) == 0) {
short d = getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
z = turnTo(z, d);
sprite->frame = z;
}
sprite->x += CDx[z];
sprite->y += CDy[z];
}
/**
* Move airplane sprite.
* @param sprite Airplane sprite.
* @todo Remove local magic constants and document the code.
* @todo absDist gets updated by Micropolis::getDir(), which is not always
* called before reading it (or worse, we just turned towards the old
* destination).
*/
void Micropolis::doAirplaneSprite(
SimSprite *sprite)
{
static const short CDx[12] = { 0, 0, 6, 8, 6, 0, -6, -8, -6, 8, 8, 8 };
static const short CDy[12] = { 0, -8, -6, 0, 6, 8, 6, 0, -6, 0, 0, 0 };
short z = sprite->frame;
if ((spriteCycle % 5) == 0) {
if (z > 8) { /* TakeOff */
z--;
if (z < 9) {
z = 3;
}
sprite->frame = z;
} else { /* goto destination */
short d = getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
z = turnTo(z, d);
sprite->frame = z;
}
}
if (absDist < 50) { /* at destination */
sprite->destX = getRandom((WORLD_W * 16) + 100) - 50;
sprite->destY = getRandom((WORLD_H * 16) + 100) - 50;
}
/* deh added test for enableDisasters */
if (enableDisasters) {
SimSprite *s;
bool explode = false;
/* Check whether another sprite is near enough to collide with */
for (s = spriteList; s != NULL; s = s->next) {
if (s->frame == 0 || s == sprite) {
/* Non-active sprite, or self: skip */
continue;
}
if ((s->type == SPRITE_HELICOPTER || s->type == SPRITE_AIRPLANE)
&& checkSpriteCollision(sprite, s)) {
explodeSprite(s);
explode = true;
}
}
if (explode) {
explodeSprite(sprite);
}
}
sprite->x += CDx[z];
sprite->y += CDy[z];
if (spriteNotInBounds(sprite)) {
sprite->frame = 0;
}
}
/**
* Move ship sprite.
* @param sprite Ship sprite.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doShipSprite(SimSprite *sprite)
{
static const short BDx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
static const short BDy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
static const short BPx[9] = { 0, 0, 2, 2, 2, 0, -2, -2, -2 };
static const short BPy[9] = { 0, -2, -2, 0, 2, 2, 2, 0, -2 };
static const short BtClrTab[8] = { RIVER, CHANNEL, POWERBASE, POWERBASE + 1,
RAILBASE, RAILBASE + 1, BRWH, BRWV };
short x, y, z, t = RIVER;
short tem, pem;
if (sprite->soundCount > 0) {
sprite->soundCount--;
}
if (!sprite->soundCount) {
if ((getRandom16() & 3) == 1) {
// Convert sprite coordinates to tile coordinates.
int shipX = sprite->x >>4;
int shipY = sprite->y >>4;
if (scenario == SC_SAN_FRANCISCO && getRandom(10) < 5) {
makeSound("city", "FogHornLow", shipX, shipY);
} else {
makeSound("city", "HonkHonkLow", shipX, shipY);
}
}
sprite->soundCount = 200;
}
if (sprite->count > 0) {
sprite->count--;
}
if (sprite->count == 0) {
sprite->count = 9;
if (sprite->frame != sprite->newDir) {
sprite->frame = turnTo(sprite->frame, sprite->newDir);
return;
}
tem = getRandom16() & 7;
for (pem = tem; pem < (tem + 8); pem++) {
z = (pem & 7) + 1;
if (z == sprite->dir) {
continue;
}
x = ((sprite->x + (48 - 1)) >>4) + BDx[z];
y = (sprite->y >>4) + BDy[z];
if (testBounds(x, y)) {
t = map[x][y] & LOMASK;
if (t == CHANNEL || t == BRWH || t == BRWV
|| tryOther(t, sprite->dir, z)) {
sprite->newDir = z;
sprite->frame = turnTo(sprite->frame, sprite->newDir);
sprite->dir = z + 4;
if (sprite->dir > 8) {
sprite->dir -= 8;
}
break;
}
}
}
if (pem == (tem + 8)) {
sprite->dir = 10;
sprite->newDir = (getRandom16() & 7) + 1;
}
} else {
z = sprite->frame;
if (z == sprite->newDir) {
sprite->x += BPx[z];
sprite->y += BPy[z];
}
}
if (spriteNotInBounds(sprite)) {
sprite->frame = 0;
return;
}
for (z = 0; z < 8; z++) {
if (t == BtClrTab[z]) {
break;
}
if (z == 7) {
explodeSprite(sprite);
destroyMapTile(sprite->x + 48, sprite->y);
}
}
}
/**
* Move monster sprite.
*
* There are 16 monster sprite frames:
*
* Frame 0: NorthEast Left Foot
* Frame 1: NorthEast Both Feet
* Frame 2: NorthEast Right Foot
* Frame 3: SouthEast Right Foot
* Frame 4: SouthEast Both Feet
* Frame 5: SouthEast Left Foot
* Frame 6: SouthWest Right Foot
* Frame 7: SouthWest Both Feet
* Frame 8: SouthWest Left Foot
* Frame 9: NorthWest Left Foot
* Frame 10: NorthWest Both Feet
* Frame 11: NorthWest Right Foot
* Frame 12: North Left Foot
* Frame 13: East Left Foot
* Frame 14: South Right Foot
* Frame 15: West Right Foot
*
* @param sprite Monster sprite.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doMonsterSprite(SimSprite *sprite)
{
static const short Gx[5] = { 2, 2, -2, -2, 0 };
static const short Gy[5] = { -2, 2, 2, -2, 0 };
static const short ND1[4] = { 0, 1, 2, 3 };
static const short ND2[4] = { 1, 2, 3, 0 };
static const short nn1[4] = { 2, 5, 8, 11 };
static const short nn2[4] = { 11, 2, 5, 8 };
short d, z, c;
if (sprite->soundCount > 0) {
sprite->soundCount--;
}
if (sprite->control < 0) {
/* business as usual */
if (sprite->control == -2) {
d = (sprite->frame - 1) / 3;
z = (sprite->frame - 1) % 3;
if (z == 2) {
sprite->step = 0;
}
if (z == 0) {
sprite->step = 1;
}
if (sprite->step) {
z++;
} else {
z--;
}
c = getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
if (absDist < 18) {
sprite->control = -1;
sprite->count = 1000;
sprite->flag = 1;
sprite->destX = sprite->origX;
sprite->destY = sprite->origY;
} else {
c = (c - 1) / 2;
if ((c != d && getRandom(5) == 0) || getRandom(20) == 0) {
int diff = (c - d) & 3;
if (diff == 1 || diff == 3) {
d = c;
} else {
if (getRandom16() & 1) {
d++;
} else {
d--;
}
d &= 3;
}
} else {
if (getRandom(20) == 0) {
if (getRandom16() & 1) {
d++;
} else {
d--;
}
d &= 3;
}
}
}
} else {
d = (sprite->frame - 1) / 3;
if (d < 4) { /* turn n s e w */
z = (sprite->frame - 1) % 3;
if (z == 2) {
sprite->step = 0;
}
if (z == 0) {
sprite->step = 1;
}
if (sprite->step) {
z++;
} else {
z--;
}
getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
if (absDist < 60) {
if (sprite->flag == 0) {
sprite->flag = 1;
sprite->destX = sprite->origX;
sprite->destY = sprite->origY;
} else {
sprite->frame = 0;
return;
}
}
c = getDir(sprite->x, sprite->y, sprite->destX, sprite->destY);
c = (c - 1) / 2;
if ((c != d) && (!getRandom(10))) {
if (getRandom16() & 1) {
z = ND1[d];
} else {
z = ND2[d];
}
d = 4;
if (!sprite->soundCount) {
// Convert sprite coordinates to tile coordinates.
int monsterX = sprite->x >>4;
int monsterY = sprite->y >>4;
makeSound("city", "Monster", monsterX, monsterY); /* monster */
sprite->soundCount = 50 + getRandom(100);
}
}
} else {
d = 4;
c = sprite->frame;
z = (c - 13) & 3;
if (!(getRandom16() & 3)) {
if (getRandom16() & 1) {
z = nn1[z];
} else {
z = nn2[z];
}
d = (z - 1) / 3;
z = (z - 1) % 3;
}
}
}
} else {
/* somebody's taken control of the monster */
d = sprite->control;
z = (sprite->frame - 1) % 3;
if (z == 2) {
sprite->step = 0;
}
if (z == 0) {
sprite->step = 1;
}
if (sprite->step) {
z++;
} else {
z--;
}
}
z = d * 3 + z + 1;
if (z > 16) {
z = 16;
}
sprite->frame = z;
sprite->x += Gx[d];
sprite->y += Gy[d];
if (sprite->count > 0) {
sprite->count--;
}
c = getChar(sprite->x + sprite->xHot, sprite->y + sprite->yHot);
if (c == -1
|| (c == RIVER && sprite->count != 0 && sprite->control == -1)) {
sprite->frame = 0; /* kill scary monster */
}
{
SimSprite *s;
for (s = spriteList; s != NULL; s = s->next) {
if (s->frame != 0 &&
(s->type == SPRITE_AIRPLANE || s->type == SPRITE_HELICOPTER
|| s->type == SPRITE_SHIP || s->type == SPRITE_TRAIN) &&
checkSpriteCollision(sprite, s)) {
explodeSprite(s);
}
}
}
destroyMapTile(sprite->x + 48, sprite->y + 16);
}
/**
* Move tornado.
* @param sprite Tornado sprite to move.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doTornadoSprite(SimSprite *sprite)
{
static const short CDx[9] = { 2, 3, 2, 0, -2, -3 };
static const short CDy[9] = { -2, 0, 2, 3, 2, 0 };
short z;
z = sprite->frame;
if (z == 2) {
/* cycle animation... post Rel */
if (sprite->flag) {
z = 3;
} else {
z = 1;
}
} else {
if (z == 1) {
sprite->flag = 1;
} else {
sprite->flag = 0;
}
z = 2;
}
if (sprite->count > 0) {
sprite->count--;
}
sprite->frame = z;
{
SimSprite *s;
for (s = spriteList; s != NULL; s = s->next) {
if (s->frame != 0 &&
(s->type == SPRITE_AIRPLANE || s->type == SPRITE_HELICOPTER
|| s->type == SPRITE_SHIP || s->type == SPRITE_TRAIN) &&
checkSpriteCollision(sprite, s)) {
explodeSprite(s);
}
}
}
z = getRandom(5);
sprite->x += CDx[z];
sprite->y += CDy[z];
if (spriteNotInBounds(sprite)) {
sprite->frame = 0;
}
if (sprite->count != 0 && getRandom(500) == 0) {
sprite->frame = 0;
}
destroyMapTile(sprite->x + 48, sprite->y + 40);
}
/**
* 'Move' fire sprite.
* @param sprite Fire sprite.
*/
void Micropolis::doExplosionSprite(SimSprite *sprite)
{
short x, y;
if ((spriteCycle & 1) == 0) {
if (sprite->frame == 1) {
// Convert sprite coordinates to tile coordinates.
int explosionX = sprite->x >>4;
int explosionY = sprite->y >>4;
makeSound("city", "ExplosionHigh", explosionX, explosionY); /* explosion */
x = (sprite->x >>4) + 3;
y = (sprite->y >>4);
sendMessage(MESSAGE_EXPLOSION_REPORTED, x, y);
}
sprite->frame++;
}
if (sprite->frame > 6) {
sprite->frame = 0;
startFire(sprite->x + 48 - 8, sprite->y + 16);
startFire(sprite->x + 48 - 24, sprite->y);
startFire(sprite->x + 48 + 8, sprite->y);
startFire(sprite->x + 48 - 24, sprite->y + 32);
startFire(sprite->x + 48 + 8, sprite->y + 32);
}
}
/**
* Move bus sprite.
* @param sprite Bus sprite.
* @todo Remove local magic constants and document the code.
*/
void Micropolis::doBusSprite(SimSprite *sprite)
{
static const short Dx[5] = { 0, 1, 0, -1, 0 };
static const short Dy[5] = { -1, 0, 1, 0, 0 };
static const short Dir2Frame[4] = { 1, 2, 1, 2 };
int dx, dy, tx, ty, otx, oty;
int turned = 0;
int speed = 0;
int z;
#ifdef DEBUGBUS
printf("Bus dir %d turn %d frame %d\n",
sprite->dir, sprite->turn, sprite->frame);
#endif
if (sprite->turn) {
if (sprite->turn < 0) { /* ccw */
if (sprite->dir & 1) { /* up or down */
sprite->frame = 4;
} else { /* left or right */
sprite->frame = 3;
}
sprite->turn++;
sprite->dir = (sprite->dir - 1) & 3;
} else { /* cw */
if (sprite->dir & 1) { /* up or down */
sprite->frame = 3;
} else { /* left or right */
sprite->frame = 4;
}
sprite->turn--;
sprite->dir = (sprite->dir + 1) & 3;
}
turned = 1;
} else {
/* finish turn */
if ((sprite->frame == 3) ||
(sprite->frame == 4)) {
turned = 1;
sprite->frame = Dir2Frame[sprite->dir];
}
}
if (sprite->speed == 0) {
/* brake */
dx = 0; dy = 0;
} else { /* cruise at traffic speed */
tx = (sprite->x + sprite->xHot) >>5;
ty = (sprite->y + sprite->yHot) >>5;
if (tx >= 0 && tx < WORLD_W_2 && ty >= 0 && ty < WORLD_H_2) {
z = trafficDensityMap.worldGet(tx << 1, ty << 1) >>6;
if (z > 1) {
z--;
}
} else {
z = 0;
}
switch (z) {
case 0:
speed = 8;
break;
case 1:
speed = 4;
break;
case 2:
speed = 1;
break;
}
/* govern speed */
if (speed > sprite->speed) {
speed = sprite->speed;
}
if (turned) {
#ifdef DEBUGBUS
printf("turned\n");
#endif
if (speed > 1) {
speed = 1;
}
dx = Dx[sprite->dir] * speed;
dy = Dy[sprite->dir] * speed;
} else {
dx = Dx[sprite->dir] * speed;
dy = Dy[sprite->dir] * speed;
tx = (sprite->x + sprite->xHot) >>4;
ty = (sprite->y + sprite->yHot) >>4;
/* drift into the right lane */
switch (sprite->dir) {
case 0: /* up */
z = ((tx <<4) + 4) - (sprite->x + sprite->xHot);
if (z < 0) {
dx = -1;
} else if (z > 0) {
dx = 1;
}
#ifdef DEBUGBUS
printf("moving up x %x z %d dx %d\n", sprite->x + sprite->xHot, z, dx);
#endif
break;
case 1: /* right */
z = ((ty <<4) + 4) - (sprite->y + sprite->yHot);
if (z < 0) {
dy = -1;
} else if (z > 0) {
dy = 1;
}
#ifdef DEBUGBUS
printf("moving right y %x z %d dy %d\n", sprite->y + sprite->yHot, z, dy);
#endif
break;
case 2: /* down */
z = (tx <<4) - (sprite->x + sprite->xHot);
if (z < 0) {
dx = -1;
} else if (z > 0) {
dx = 1;
}
#ifdef DEBUGBUS
printf("moving down x %x z %d dx %d\n", sprite->x + sprite->xHot, z, dx);
#endif
break;
case 3: /* left */
z = (ty <<4) - (sprite->y + sprite->yHot);
if (z < 0) {
dy = -1;
} else if (z > 0) {
dy = 1;
}
#ifdef DEBUGBUS
printf("moving left y %x z %d dy %d\n", sprite->y + sprite->yHot, z, dy);
#endif
break;
}
}
}
#ifdef DEBUGBUS
printf("speed dx %d dy %d\n", dx, dy);
#endif
#define AHEAD 8
otx = (sprite->x + sprite->xHot + (Dx[sprite->dir] * AHEAD)) >>4;
oty = (sprite->y + sprite->yHot + (Dy[sprite->dir] * AHEAD)) >>4;
otx = clamp(otx, 0, WORLD_W - 1);
oty = clamp(oty, 0, WORLD_H - 1);
tx = (sprite->x + sprite->xHot + dx + (Dx[sprite->dir] * AHEAD)) >>4;
ty = (sprite->y + sprite->yHot + dy + (Dy[sprite->dir] * AHEAD)) >>4;
tx = clamp(tx, 0, WORLD_W - 1);
ty = clamp(ty, 0, WORLD_H - 1);
if (tx != otx || ty != oty) {
#ifdef DEBUGBUS
printf("drive from tile %d %d to %d %d\n",
otx, oty, tx, ty);
#endif
z = canDriveOn(tx, ty);
if (z == 0) {
/* can't drive forward into a new tile */
if (speed == 8) {
bulldozerTool(tx, ty);
} else {
}
} else {
/* drive forward into a new tile */
if (z > 0) {
/* smooth */
} else {
/* bumpy */
dx /= 2;
dy /= 2;
}
}
}
tx = (sprite->x + sprite->xHot + dx) >>4;
ty = (sprite->y + sprite->yHot + dy) >>4;
z = canDriveOn(tx, ty);
if (z > 0) {
/* cool, cruise along */
} else {
if (z < 0) {
/* bumpy */
} else {
/* something in the way */
}
}
sprite->x += dx;
sprite->y += dy;
if (enableDisasters) {
SimSprite *s;
int explode = 0;
for (s = spriteList; s != NULL; s = s->next) {
if (sprite != s && s->frame != 0
&& (s->type == SPRITE_BUS
|| (s->type == SPRITE_TRAIN && s->frame != 5))
&& checkSpriteCollision(sprite, s)) {
explodeSprite(s);
explode = 1;
}
}
if (explode) {
explodeSprite(sprite);
}
}
}
/**
* Can one drive at the specified tile?
* @param x X coordinate at map.
* @param y Y coordinate at map.
* @return 0 if not, 1 if you can, -1 otherwise
*/
int Micropolis::canDriveOn(int x, int y)
{
int tile;
if (!testBounds(x, y)) {
return 0;
}
tile = map[x][y] & LOMASK;
if ((tile >= ROADBASE && tile <= LASTROAD && tile != BRWH && tile != BRWV)
|| tile == HRAILROAD || tile == VRAILROAD) {
return 1;
}
if (tile == DIRT || tally(tile)) {
return -1;
}
return 0;
}
/**
* Handle explosion of sprite (mostly due to collision?).
* @param sprite that should explode.
* @todo Add a 'bus crashed' message to #MessageNumber.
*/
void Micropolis::explodeSprite(SimSprite *sprite)
{
int x, y;
sprite->frame = 0;
x = sprite->x + sprite->xHot;
y = sprite->y + sprite->yHot;
makeExplosionAt(x, y);
x = (x >>4);
y = (y >>4);
switch (sprite->type) {
case SPRITE_AIRPLANE:
sendMessage(MESSAGE_PLANE_CRASHED, x, y, true);
break;
case SPRITE_SHIP:
sendMessage(MESSAGE_SHIP_CRASHED, x, y, true);
break;
case SPRITE_TRAIN:
sendMessage(MESSAGE_TRAIN_CRASHED, x, y, true);
break;
case SPRITE_HELICOPTER:
sendMessage(MESSAGE_HELICOPTER_CRASHED, x, y, true);
break;
case SPRITE_BUS:
sendMessage(MESSAGE_TRAIN_CRASHED, x, y, true); /* XXX for now */
break;
}
// Convert sprite coordinates to tile coordinates.
makeSound("city", "ExplosionHigh", x, y); /* explosion */
return;
}
bool Micropolis::checkWet(int x)
{
if (x == HPOWER || x == VPOWER || x == HRAIL || x == VRAIL
|| x == BRWH || x == BRWV) {
return true;
} else {
return false;
}
}
/**
* Destroy a map tile.
* @param ox X coordinate in pixels.
* @param oy Y coordinate in pixels.
*/
void Micropolis::destroyMapTile(int ox, int oy)
{
short t, z, x, y;
x = ox >>4;
y = oy >>4;
if (!testBounds(x, y)) {
return;
}
z = map[x][y];
t = z & LOMASK;
if (t >= TREEBASE) {
if (!(z & BURNBIT)) {
if (t >= ROADBASE && t <= LASTROAD) {
map[x][y] = RIVER;
}
return;
}
if (z & ZONEBIT) {
startFireInZone(x, y, z);
if (t > RZB) {
makeExplosionAt(ox, oy);
}
}
if (checkWet(t)) {
map[x][y] = RIVER;
} else {
map[x][y] = (doAnimation ? TINYEXP : (LASTTINYEXP - 3))
| BULLBIT | ANIMBIT;
}
}
}
/**
* Start a fire in a zone.
* @param Xloc X coordinate in map coordinate.
* @param Yloc Y coordinate in map coordinate.
* @param ch Map character at (\a Xloc, \a Yloc).
*/
void Micropolis::startFireInZone(int Xloc, int Yloc, int ch)
{
short Xtem, Ytem;
short x, y, XYmax;
int value = rateOfGrowthMap.worldGet(Xloc, Yloc);
value = clamp(value - 20, -200, 200);
rateOfGrowthMap.worldSet(Xloc, Yloc, value);
ch &= LOMASK;
if (ch < PORTBASE) {
XYmax = 2;
} else {
if (ch == AIRPORT) {
XYmax = 5;
} else {
XYmax = 4;
}
}
for (x = -1; x < XYmax; x++) {
for (y = -1; y < XYmax; y++) {
Xtem = Xloc + x;
Ytem = Yloc + y;
if (testBounds(Xtem, Ytem) && (map[Xtem][Ytem] & LOMASK) >= ROADBASE) {
map[Xtem][Ytem] |= BULLBIT;
}
}
}
}
/**
* Start a fire at a single tile.
* @param x X coordinate in map coordinate.
* @param y Y coordinate in map coordinate.
*/
void Micropolis::startFire(int x, int y)
{
int t, z;
x >>= 4;
y >>= 4;
if (!testBounds(x, y)) {
return;
}
z = map[x][y];
t = z & LOMASK;
if (!(z & BURNBIT) && t != DIRT) {
return;
}
if (z & ZONEBIT) {
return;
}
map[x][y] = randomFire();
}
/**
* Try to start a new train sprite at the given map tile.
* @param x X coordinate in map coordinate.
* @param y Y coordinate in map coordinate.
*/
void Micropolis::generateTrain(int x, int y)
{
if (totalPop > 20 && getSprite(SPRITE_TRAIN) == NULL && getRandom(25) == 0) {
makeSprite(SPRITE_TRAIN, (x <<4) + TRA_GROOVE_X, (y <<4) + TRA_GROOVE_Y);
}
}
/**
* Try to start a new bus sprite at the given map tile.
* @param x X coordinate in map coordinate.
* @param y Y coordinate in map coordinate.
*/
void Micropolis::generateBus(int x, int y)
{
if (getSprite(SPRITE_BUS) == NULL && getRandom(25) == 0) {
makeSprite(SPRITE_BUS, (x <<4) + BUS_GROOVE_X, (y <<4) + BUS_GROOVE_Y);
}
}
/** Try to construct a new ship sprite */
void Micropolis::generateShip()
{
short x, y;
if (!(getRandom16() & 3)) {
for (x = 4; x < WORLD_W - 2; x++) {
if (map[x][0] == CHANNEL) {
makeShipHere(x, 0);
return;
}
}
}
if (!(getRandom16() & 3)) {
for (y = 1; y < WORLD_H - 2; y++) {
if (map[0][y] == CHANNEL) {
makeShipHere(0, y);
return;
}
}
}
if (!(getRandom16() & 3)) {
for (x = 4; x < WORLD_W - 2; x++) {
if (map[x][WORLD_H - 1] == CHANNEL) {
makeShipHere(x, WORLD_H - 1);
return;
}
}
}
if (!(getRandom16() & 3)) {
for (y = 1; y < WORLD_H - 2; y++) {
if (map[WORLD_W - 1][y] == CHANNEL) {
makeShipHere(WORLD_W - 1, y);
return;
}
}
}
}
/**
* Start a new ship sprite at the given map tile.
* @param x X coordinate in map coordinate.
* @param y Y coordinate in map coordinate.
*/
void Micropolis::makeShipHere(int x, int y)
{
makeSprite(SPRITE_SHIP, (x <<4) - (48 - 1), (y <<4));
}
/**
* Start a new monster sprite.
* @todo Make monster over land, because it disappears if it's made over water.
* Better yet make monster not disappear for a while after it's created,
* over land or water. Should never disappear prematurely.
*/
void Micropolis::makeMonster()
{
int x, y, z, done = 0;
SimSprite *sprite;
sprite = getSprite(SPRITE_MONSTER);
if (sprite != NULL) {
sprite->soundCount = 1;
sprite->count = 1000;
sprite->destX = pollutionMaxX <<4;
sprite->destY = pollutionMaxY <<4;
return;
}
for (z = 0; z < 300; z++) {
x = getRandom(WORLD_W - 20) + 10;
y = getRandom(WORLD_H - 10) + 5;
if (map[x][y] == RIVER || map[x][y] == RIVER + BULLBIT) {
makeMonsterAt(x, y);
done = 1;
break;
}
}
if (!done) {
makeMonsterAt(60, 50);
}
}
/**
* Start a new monster sprite at the given map tile.
* @param x X coordinate in map coordinate.
* @param y Y coordinate in map coordinate.
*/
void Micropolis::makeMonsterAt(int x, int y)
{
makeSprite(SPRITE_MONSTER, (x << 4) + 48, (y << 4));
sendMessage(MESSAGE_MONSTER_SIGHTED, x + 5, y, true, true);
}
/**
* Ensure a helicopter sprite exists.
*
* If it does not exist, create one at the given coordinates.
* @param pos Start position in map coordinates.
*/
void Micropolis::generateCopter(const Position &pos)
{
if (getSprite(SPRITE_HELICOPTER) != NULL) {
return;
}
makeSprite(SPRITE_HELICOPTER, (pos.posX << 4), (pos.posY << 4) + 30);
}
/**
* Ensure an airplane sprite exists.
*
* If it does not exist, create one at the given coordinates.
* @param pos Start position in map coordinates.
*/
void Micropolis::generatePlane(const Position &pos)
{
if (getSprite(SPRITE_AIRPLANE) != NULL) {
return;
}
makeSprite(SPRITE_AIRPLANE, (pos.posX <<4) + 48, (pos.posY <<4) + 12);
}
/** Ensure a tornado sprite exists. */
void Micropolis::makeTornado()
{
short x, y;
SimSprite *sprite;
sprite = getSprite(SPRITE_TORNADO);
if (sprite != NULL) {
sprite->count = 200;
return;
}
x = getRandom((WORLD_W <<4) - 800) + 400;
y = getRandom((WORLD_H <<4) - 200) + 100;
makeSprite(SPRITE_TORNADO, x, y);
sendMessage(MESSAGE_TORNADO_SIGHTED, (x >>4) + 3, (y >>4) + 2, true, true);
}
/**
* Construct an explosion sprite.
* @param x X coordinate of the explosion (in map coordinates).
* @param y Y coordinate of the explosion (in map coordinates).
*/
void Micropolis::makeExplosion(int x, int y)
{
if (testBounds(x, y)) {
makeExplosionAt((x << 4) + 8, (y << 4) + 8);
}
}
/**
* Construct an explosion sprite.
* @param x X coordinate of the explosion (in pixels).
* @param y Y coordinate of the explosion (in pixels).
*/
void Micropolis::makeExplosionAt( int x, int y)
{
newSprite("", SPRITE_EXPLOSION, x - 40, y - 16);
}
////////////////////////////////////////////////////////////////////////