warzone2100/src/atmos.cpp

355 lines
8.6 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 Warzone 2100 Project
Warzone 2100 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 2 of the License, or
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file atmos.c
*
* Handles atmospherics such as snow and rain.
*/
#include "lib/framework/frame.h"
#include "lib/ivis_opengl/piematrix.h"
#include "atmos.h"
#include "display3d.h"
#include "effects.h"
#include "hci.h"
#include "loop.h"
#include "map.h"
#include "miscimd.h"
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Shift all this gubbins into a .h file if it makes it into game
// -----------------------------------------------------------------------------
/* Roughly one per tile */
#define MAX_ATMOS_PARTICLES (MAP_MAXWIDTH * MAP_MAXHEIGHT)
#define SNOW_SPEED_DRIFT (40 - rand() % 80)
#define SNOW_SPEED_FALL (0 - (rand() % 40 + 80))
#define RAIN_SPEED_DRIFT (rand() % 50)
#define RAIN_SPEED_FALL (0 - ((rand() % 300) + 700))
enum AP_TYPE
{
AP_RAIN,
AP_SNOW
};
enum AP_STATUS
{
APS_INACTIVE,
APS_ACTIVE
};
static ATPART *asAtmosParts = NULL;
static UDWORD freeParticle;
static WT_CLASS weather = WT_NONE;
/* Setup all the particles */
void atmosInitSystem()
{
if (!asAtmosParts && weather != WT_NONE)
{
// calloc sets all to APS_INACTIVE initially
asAtmosParts = (ATPART *)calloc(MAX_ATMOS_PARTICLES, sizeof(*asAtmosParts));
}
/* Start at the beginning */
freeParticle = 0;
}
/* Makes a particle wrap around - if it goes off the grid, then it returns
on the other side - provided it's still on world... Which it should be */
static void testParticleWrap(ATPART *psPart)
{
/* Gone off left side */
if (psPart->position.x < player.p.x - world_coord(visibleTiles.x) / 2)
{
psPart->position.x += world_coord(visibleTiles.x);
}
/* Gone off right side */
else if (psPart->position.x > (player.p.x + world_coord(visibleTiles.x) / 2))
{
psPart->position.x -= world_coord(visibleTiles.x);
}
/* Gone off top */
if (psPart->position.z < player.p.z - world_coord(visibleTiles.y) / 2)
{
psPart->position.z += world_coord(visibleTiles.y);
}
/* Gone off bottom */
else if (psPart->position.z > (player.p.z + world_coord(visibleTiles.y) / 2))
{
psPart->position.z -= world_coord(visibleTiles.y);
}
}
/* Moves one of the particles */
static void processParticle(ATPART *psPart)
{
SDWORD groundHeight;
Vector3i pos;
UDWORD x, y;
MAPTILE *psTile;
/* Only move if the game isn't paused */
if (!gamePaused())
{
/* Move the particle - frame rate controlled */
psPart->position.x += graphicsTimeAdjustedIncrement(psPart->velocity.x);
psPart->position.y += graphicsTimeAdjustedIncrement(psPart->velocity.y);
psPart->position.z += graphicsTimeAdjustedIncrement(psPart->velocity.z);
/* Wrap it around if it's gone off grid... */
testParticleWrap(psPart);
/* If it's gone off the WORLD... */
if (psPart->position.x < 0 || psPart->position.z < 0 ||
psPart->position.x > ((mapWidth - 1)*TILE_UNITS) ||
psPart->position.z > ((mapHeight - 1)*TILE_UNITS))
{
/* The kill it */
psPart->status = APS_INACTIVE;
return;
}
/* What height is the ground under it? Only do if low enough...*/
if (psPart->position.y < 255 * ELEVATION_SCALE)
{
/* Get ground height */
groundHeight = map_Height(psPart->position.x, psPart->position.z);
/* Are we below ground? */
if ((int)psPart->position.y < groundHeight
|| psPart->position.y < 0.f)
{
/* Kill it and return */
psPart->status = APS_INACTIVE;
if (psPart->type == AP_RAIN)
{
x = map_coord(psPart->position.x);
y = map_coord(psPart->position.z);
psTile = mapTile(x, y);
if (terrainType(psTile) == TER_WATER && TEST_TILE_VISIBLE(selectedPlayer, psTile))
{
pos.x = psPart->position.x;
pos.z = psPart->position.z;
pos.y = groundHeight;
effectSetSize(60);
addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_SPECIFIED, true, getImdFromIndex(MI_SPLASH), 0);
}
}
return;
}
}
if (psPart->type == AP_SNOW)
{
if (rand() % 30 == 1)
{
psPart->velocity.z = (float)SNOW_SPEED_DRIFT;
}
if (rand() % 30 == 1)
{
psPart->velocity.x = (float)SNOW_SPEED_DRIFT;
}
}
}
}
/* Adds a particle to the system if it can */
static void atmosAddParticle(const Vector3f &pos, AP_TYPE type)
{
UDWORD activeCount;
UDWORD i;
for (i = freeParticle, activeCount = 0; asAtmosParts[i].status == APS_ACTIVE && activeCount < MAX_ATMOS_PARTICLES; i++)
{
activeCount++;
/* Check for wrap around */
if (i >= (MAX_ATMOS_PARTICLES - 1))
{
/* Go back to the first one */
i = 0;
}
}
/* Check the list isn't just full of essential effects */
if (activeCount >= MAX_ATMOS_PARTICLES - 1)
{
/* All of the particles active!?!? */
return;
}
else
{
freeParticle = i;
}
/* Record it's type */
asAtmosParts[freeParticle].type = (UBYTE)type;
/* Make it active */
asAtmosParts[freeParticle].status = APS_ACTIVE;
/* Setup the imd */
switch (type)
{
case AP_SNOW:
asAtmosParts[freeParticle].imd = getImdFromIndex(MI_SNOW);
asAtmosParts[freeParticle].size = 80;
break;
case AP_RAIN:
asAtmosParts[freeParticle].imd = getImdFromIndex(MI_RAIN);
asAtmosParts[freeParticle].size = 50;
break;
default:
break;
}
/* Setup position */
asAtmosParts[freeParticle].position = pos;
/* Setup its velocity */
if (type == AP_RAIN)
{
asAtmosParts[freeParticle].velocity = Vector3f(RAIN_SPEED_DRIFT, RAIN_SPEED_FALL, RAIN_SPEED_DRIFT);
}
else
{
asAtmosParts[freeParticle].velocity = Vector3f(SNOW_SPEED_DRIFT, SNOW_SPEED_FALL, SNOW_SPEED_DRIFT);
}
}
/* Move the particles */
void atmosUpdateSystem()
{
UDWORD i;
UDWORD numberToAdd;
Vector3f pos;
// we don't want to do any of this while paused.
if (!gamePaused() && weather != WT_NONE)
{
for (i = 0; i < MAX_ATMOS_PARTICLES; i++)
{
/* See if it's active */
if (asAtmosParts[i].status == APS_ACTIVE)
{
processParticle(&asAtmosParts[i]);
}
}
/* This bit below needs to go into a "precipitation function" */
numberToAdd = ((weather == WT_SNOWING) ? 2 : 4);
/* Temporary stuff - just adds a few particles! */
for (i = 0; i < numberToAdd; i++)
{
pos.x = player.p.x;
pos.z = player.p.z;
pos.x += world_coord(rand() % visibleTiles.x - visibleTiles.x / 2);
pos.z += world_coord(rand() % visibleTiles.x - visibleTiles.y / 2);
pos.y = 1000;
/* If we've got one on the grid */
if (pos.x > 0 && pos.z > 0 &&
pos.x < (SDWORD)world_coord(mapWidth - 1) &&
pos.z < (SDWORD)world_coord(mapHeight - 1))
{
/* On grid, so which particle shall we add? */
switch (weather)
{
case WT_SNOWING:
atmosAddParticle(pos, AP_SNOW);
break;
case WT_RAINING:
atmosAddParticle(pos, AP_RAIN);
break;
case WT_NONE:
break;
}
}
}
}
}
void atmosDrawParticles()
{
UDWORD i;
if (weather == WT_NONE)
{
return;
}
/* Traverse the list */
for (i = 0; i < MAX_ATMOS_PARTICLES; i++)
{
/* Don't bother unless it's active */
if (asAtmosParts[i].status == APS_ACTIVE)
{
/* Is it on the grid */
if (clipXY(asAtmosParts[i].position.x, asAtmosParts[i].position.z))
{
renderParticle(&asAtmosParts[i]);
}
}
}
}
void renderParticle(ATPART *psPart)
{
Vector3i dv;
/* Transform it */
dv.x = psPart->position.x - player.p.x;
dv.y = psPart->position.y;
dv.z = -(psPart->position.z - player.p.z);
pie_MatBegin(); /* Push the current matrix */
pie_TRANSLATE(dv.x, dv.y, dv.z);
/* Make it face camera */
pie_MatRotY(-player.r.y);
pie_MatRotY(-player.r.x);
/* Scale it... */
pie_MatScale(psPart->size / 100.f);
/* Draw it... */
pie_Draw3DShape(psPart->imd, 0, 0, WZCOL_WHITE, 0, 0);
pie_MatEnd();
}
void atmosSetWeatherType(WT_CLASS type)
{
if (type != weather)
{
weather = type;
atmosInitSystem();
}
if (type == WT_NONE && asAtmosParts)
{
free(asAtmosParts);
asAtmosParts = NULL;
}
}
WT_CLASS atmosGetWeatherType()
{
return weather;
}