warzone2100/src/cluster.cpp

491 lines
11 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
*/
/*
* Cluster.c
*
* Form droids and structures into clusters
*
*/
#include "lib/framework/frame.h"
#include "lib/script/script.h"
#include "cluster.h"
#include "map.h"
#include "scriptcb.h"
#include "scripttabs.h"
#include "projectile.h"
#include "qtscript.h"
// distance between units for them to be in the same cluster
#define CLUSTER_DIST (TILE_UNITS*8)
// Indirect the cluster ID to an actual cluster number
UBYTE aClusterMap[CLUSTER_MAX];
// flag to note when a cluster needs the cluster empty callback
static UBYTE aClusterEmpty[CLUSTER_MAX];
// number of droids in a cluster
static UWORD aClusterUsage[CLUSTER_MAX];
// whether a cluster can be seen by a player
static PlayerMask aClusterVisibility[CLUSTER_MAX];
// when a cluster was last attacked
static UDWORD aClusterAttacked[CLUSTER_MAX];
// information about the cluster
UBYTE aClusterInfo[CLUSTER_MAX];
// initialise the cluster system
void clustInitialise(void)
{
DROID *psDroid;
STRUCTURE *psStruct;
SDWORD player;
ASSERT( CLUSTER_MAX <= UBYTE_MAX,
"clustInitialse: invalid CLUSTER_MAX, this is a BUILD error" );
memset(aClusterMap, 0, sizeof(UBYTE) * CLUSTER_MAX);
memset(aClusterEmpty, 0, sizeof(UBYTE) * CLUSTER_MAX);
memset(aClusterUsage, 0, sizeof(UWORD) * CLUSTER_MAX);
memset(aClusterVisibility, 0, sizeof(UBYTE) * CLUSTER_MAX);
memset(aClusterAttacked, 0, sizeof(UDWORD) * CLUSTER_MAX);
memset(aClusterInfo, 0, sizeof(UBYTE) * CLUSTER_MAX);
for(player=0; player<MAX_PLAYERS; player++)
{
for(psDroid=apsDroidLists[player]; psDroid; psDroid=psDroid->psNext)
{
psDroid->cluster = 0;
}
for(psStruct=apsStructLists[player]; psStruct; psStruct=psStruct->psNext)
{
psStruct->cluster = 0;
}
for(psStruct=apsStructLists[player]; psStruct; psStruct=psStruct->psNext)
{
if (psStruct->cluster == 0)
{
clustUpdateObject((BASE_OBJECT *)psStruct);
}
}
}
}
// update routine for the cluster system
void clusterUpdate(void)
{
SDWORD i;
for(i=1; i< CLUSTER_MAX; i++)
{
if (aClusterEmpty[i])
{
scrCBEmptyClusterID = i;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_CLUSTER_EMPTY);
aClusterEmpty[i] = false;
}
}
}
// update all objects from a list belonging to a specific cluster
void clustUpdateCluster(BASE_OBJECT *psList, SDWORD cluster)
{
BASE_OBJECT *psCurr;
if (cluster == 0)
{
return;
}
for(psCurr = psList; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster == cluster)
{
clustUpdateObject(psCurr);
}
}
}
// remove an object from the cluster system
void clustRemoveObject(BASE_OBJECT *psObj)
{
SDWORD i;
ASSERT( psObj->cluster < CLUSTER_MAX,
"clustRemoveObject: invalid cluster number" );
// update the usage counter
if (psObj->cluster != 0)
{
ASSERT( aClusterUsage[psObj->cluster] > 0,
"clustRemoveObject: usage array out of sync" );
aClusterUsage[psObj->cluster] -= 1;
if (aClusterUsage[psObj->cluster] == 0)
{
// cluster is empty - make sure the cluster map gets emptied
for (i=0; i<CLUSTER_MAX; i++)
{
if (aClusterMap[i] == psObj->cluster)
{
aClusterMap[i] = 0;
if (i != 0)
{
// debug( LOG_NEVER, "Cluster %d empty: ", i );
// debug( LOG_NEVER, "%s ", (psObj->type == OBJ_DROID) ? "Unit" : ( (psObj->type == OBJ_STRUCTURE) ? "Struct" : "Feat") );
// debug( LOG_NEVER, "id %d player %d\n", psObj->id, psObj->player );
aClusterEmpty[i] = true;
}
}
}
// reset the cluster visibility and attacked
aClusterVisibility[psObj->cluster] = 0;
aClusterAttacked[psObj->cluster] = 0;
aClusterInfo[psObj->cluster] = 0;
}
}
psObj->cluster = 0;
}
// tell a droid to join a cluster
static void clustAddDroid(DROID *psDroid, SDWORD cluster)
{
DROID *psCurr;
SDWORD xdiff, ydiff;
SDWORD player;
clustRemoveObject((BASE_OBJECT *)psDroid);
aClusterUsage[cluster] += 1;
psDroid->cluster = (UBYTE)cluster;
for(player=0; player<MAX_PLAYERS; player++)
{
if (psDroid->visible[player])
{
aClusterVisibility[cluster] |= (1 << player);
}
}
for(psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster == (UBYTE)cluster)
{
continue;
}
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psCurr->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psCurr->pos.y;
if (xdiff*xdiff + ydiff*ydiff < CLUSTER_DIST*CLUSTER_DIST)
{
clustAddDroid(psCurr, cluster);
}
}
}
// tell the cluster system about a new droid
void clustNewDroid(DROID *psDroid)
{
DROID *psCurr;
SDWORD xdiff, ydiff;
psDroid->cluster = 0;
for(psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster != 0)
{
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psCurr->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psCurr->pos.y;
if (xdiff*xdiff + ydiff*ydiff < CLUSTER_DIST*CLUSTER_DIST)
{
clustAddDroid(psDroid, psCurr->cluster);
return;
}
}
}
}
// tell a structure to join a cluster
static void clustAddStruct(STRUCTURE *psStruct, SDWORD cluster)
{
STRUCTURE *psCurr;
SDWORD xdiff, ydiff;
SDWORD player;
clustRemoveObject((BASE_OBJECT *)psStruct);
aClusterUsage[cluster] += 1;
psStruct->cluster = (UBYTE)cluster;
for(player=0; player<MAX_PLAYERS; player++)
{
if (psStruct->visible[player])
{
aClusterVisibility[cluster] |= (1 << player);
}
}
for(psCurr = apsStructLists[psStruct->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster == (UBYTE)cluster)
{
continue;
}
xdiff = (SDWORD)psStruct->pos.x - (SDWORD)psCurr->pos.x;
ydiff = (SDWORD)psStruct->pos.y - (SDWORD)psCurr->pos.y;
if (xdiff*xdiff + ydiff*ydiff < CLUSTER_DIST*CLUSTER_DIST)
{
clustAddStruct(psCurr, cluster);
}
}
}
// tell the cluster system about a new structure
void clustNewStruct(STRUCTURE *psStruct)
{
STRUCTURE *psCurr;
SDWORD xdiff, ydiff;
psStruct->cluster = 0;
for(psCurr = apsStructLists[psStruct->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster != 0)
{
xdiff = (SDWORD)psStruct->pos.x - (SDWORD)psCurr->pos.x;
ydiff = (SDWORD)psStruct->pos.y - (SDWORD)psCurr->pos.y;
if (xdiff*xdiff + ydiff*ydiff < CLUSTER_DIST*CLUSTER_DIST)
{
psStruct->cluster = psCurr->cluster;
aClusterUsage[psCurr->cluster] += 1;
break;
}
}
}
clustUpdateObject((BASE_OBJECT *)psStruct);
}
// find an unused cluster number for a droid
static SDWORD clustFindUnused(void)
{
SDWORD cluster;
for(cluster = 1; cluster < CLUSTER_MAX; cluster ++)
{
if (aClusterUsage[cluster] == 0)
{
return cluster;
}
}
// no unused cluster return the default
return 0;
}
// update the cluster information for an object
void clustUpdateObject(BASE_OBJECT * psObj)
{
SDWORD newCluster, oldCluster, i;
bool found;
SDWORD player;
newCluster = clustFindUnused();
oldCluster = psObj->cluster;
// update the cluster map
found = false;
if (oldCluster != 0)
{
for(i=0; i<CLUSTER_MAX; i++)
{
ASSERT( (aClusterMap[i] == 0) ||
(aClusterUsage[ aClusterMap[i] ] != 0),
"clustUpdateObject: cluster map out of sync" );
if (aClusterMap[i] == oldCluster)
{
// found the old cluster - change it to the new one
aClusterMap[i] = (UBYTE)newCluster;
aClusterAttacked[newCluster] = aClusterAttacked[oldCluster];
// aClusterVisibility[newCluster] = aClusterVisibility[oldCluster];
aClusterVisibility[newCluster] = 0;
for(player = 0; player < MAX_PLAYERS; player++)
{
if (psObj->visible[player])
{
aClusterVisibility[newCluster] |= 1 << player;
}
}
found = true;
break;
}
}
}
if (!found)
{
// there is no current cluster map - create a new one
for(i=1; i<CLUSTER_MAX; i++)
{
if (aClusterMap[i] == 0)
{
// found a free cluster
aClusterMap[i] = (UBYTE)newCluster;
break;
}
}
}
// store the information about this cluster
aClusterInfo[newCluster] = (UBYTE)(psObj->player & CLUSTER_PLAYER_MASK);
switch (psObj->type)
{
case OBJ_DROID:
aClusterInfo[newCluster] |= CLUSTER_DROID;
clustAddDroid((DROID *)psObj, newCluster);
break;
case OBJ_STRUCTURE:
aClusterInfo[newCluster] |= CLUSTER_STRUCTURE;
clustAddStruct((STRUCTURE *)psObj, newCluster);
break;
default:
ASSERT(!"invalid object type", "clustUpdateObject: invalid object type");
break;
}
}
// get the cluster ID for a droid
SDWORD clustGetClusterID(BASE_OBJECT *psObj)
{
SDWORD cluster;
if (psObj->cluster == 0)
{
return 0;
}
for (cluster=0; cluster < CLUSTER_MAX; cluster ++)
{
if (aClusterMap[cluster] == psObj->cluster)
{
return cluster;
}
}
return 0;
}
// tell the cluster system that an objects visibility has changed
void clustObjectSeen(BASE_OBJECT *psObj, BASE_OBJECT *psViewer)
{
SDWORD player;
for(player=0; player<MAX_PLAYERS; player++)
{
ASSERT(psObj->cluster != (UBYTE)~0, "object not in a cluster");
if ( (player != (SDWORD)psObj->player) &&
hasSharedVision(psViewer->player, player) &&
!(aClusterVisibility[psObj->cluster] & (1 << player)))
{
aClusterVisibility[psObj->cluster] |= 1 << player;
psScrCBObjSeen = psObj;
psScrCBObjViewer = psViewer;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_OBJ_SEEN);
triggerEventSeen(psViewer, psObj);
switch (psObj->type)
{
case OBJ_DROID:
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROID_SEEN);
break;
case OBJ_STRUCTURE:
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_STRUCT_SEEN);
break;
case OBJ_FEATURE:
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_FEATURE_SEEN);
break;
default:
ASSERT(!"invalid object type", "clustObjectSeen: invalid object type");
break;
}
psScrCBObjSeen = NULL;
psScrCBObjViewer = NULL;
}
}
}
// tell the cluster system that an object has been attacked
void clustObjectAttacked(BASE_OBJECT *psObj)
{
if ((aClusterAttacked[psObj->cluster] + ATTACK_CB_PAUSE) < gameTime)
{
psScrCBTarget = psObj;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_ATTACKED);
switch (psObj->type)
{
case OBJ_DROID:
psLastDroidHit = (DROID *)psObj;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROID_ATTACKED);
psLastDroidHit = NULL;
break;
case OBJ_STRUCTURE:
psLastStructHit = (STRUCTURE *)psObj;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_STRUCT_ATTACKED);
psLastStructHit = NULL;
break;
default:
ASSERT(!"invalid object type", "clustObjectAttacked: invalid object type");
return;
}
// and fire the sound effect (and/or...) for the added callback trigger we just processed
aClusterAttacked[psObj->cluster] = gameTime;
}
}
// reset the visibility for all clusters for a particular player
void clustResetVisibility(SDWORD player)
{
SDWORD i;
for(i=0; i<CLUSTER_MAX; i++)
{
aClusterVisibility[i] &= ~(1 << player);
}
}