warzone2100/src/cluster.c

707 lines
16 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection 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 <string.h>
// cluster empty printf's
#include "lib/framework/frame.h"
#include "objects.h"
#include "map.h"
#include "cluster.h"
#include "console.h"
#include "hci.h"
#include "lib/gamelib/gtime.h"
#include "lib/script/script.h"
#include "scripttabs.h"
#include "scriptcb.h"
// distance between units for them to be in the same cluster
#define CLUSTER_DIST (TILE_UNITS*8)
// cluster information flags
#define CLUSTER_PLAYER_MASK 0x07
#define CLUSTER_DROID 0x08
#define CLUSTER_STRUCTURE 0x10
// Indirect the cluster ID to an actual cluster number
UBYTE aClusterMap[CLUSTER_MAX];
// flag to note when a cluster needs the cluster empty callback
UBYTE aClusterEmpty[CLUSTER_MAX];
// number of droids in a cluster
UWORD aClusterUsage[CLUSTER_MAX];
// whether a cluster can be seen by a player
UBYTE aClusterVisibility[CLUSTER_MAX];
// when a cluster was last attacked
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);
}
}
}
static void clustAddDroid(DROID *psDroid, SDWORD cluster)
{
_clustAddDroid(psDroid,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);
}
}
}
static void clustAddStruct(STRUCTURE *psStruct, SDWORD cluster)
{
_clustAddStruct(psStruct,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;
}
// display the current clusters
void clustDisplay(void)
{
SDWORD cluster, map, player;
DROID *psDroid;
STRUCTURE *psStruct;
char aBuff[255];
BOOL found;
SDWORD numUsed;
numUsed = 0;
for(map=0; map < CLUSTER_MAX; map++)
{
if (aClusterMap[map] != 0)
{
numUsed += 1;
}
}
CONPRINTF(ConsoleString, (ConsoleString, "Current clusters (%d):\n", numUsed));
for(map=0; map < CLUSTER_MAX; map++)
{
cluster = aClusterMap[map];
if (cluster != 0)
{
found = false;
for(player = 0; player < MAX_PLAYERS; player++)
{
// if (player == (SDWORD)selectedPlayer)
// {
// continue;
// }
for(psDroid=apsDroidLists[player]; psDroid; psDroid=psDroid->psNext)
{
if ((psDroid->cluster == cluster) &&
// (psDroid->visible[selectedPlayer]))
(psDroid->player == selectedPlayer))
{
if (!found)
{
// found a cluster print it out
sprintf(aBuff, "Unit cluster %d (%d), ", map, cluster);
sprintf(aBuff + strlen(aBuff), "player %d:", psDroid->player);
found = true;
}
if (strlen(aBuff) < 250)
{
sprintf(aBuff + strlen(aBuff), " %d", psDroid->id);
}
}
}
for(psStruct=apsStructLists[player]; psStruct; psStruct=psStruct->psNext)
{
if ((psStruct->cluster == cluster) &&
// (psStruct->visible[selectedPlayer]))
(psStruct->player == selectedPlayer))
{
if (!found)
{
// found a cluster print it out
sprintf(aBuff, "struct cluster %d (%d), ", map, cluster);
sprintf(aBuff + strlen(aBuff), "player %d:", psStruct->player);
found = true;
}
if (strlen(aBuff) < 250)
{
sprintf(aBuff + strlen(aBuff), " %d", psStruct->id);
}
}
}
}
if (found)
{
CONPRINTF(ConsoleString, (ConsoleString, "%s", aBuff));
}
}
}
}
// 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;
}
// get the actual cluster number from a cluster ID
SDWORD clustGetClusterFromID(SDWORD clusterID)
{
ASSERT( (clusterID >= 0) && (clusterID < CLUSTER_MAX),
"clustGetClusterFromID: invalid cluster ID" );
return aClusterMap[clusterID];
}
// variables for the cluster iteration
static SDWORD iterateClusterID;
static BASE_OBJECT *psIterateList, *psIterateObj;
// initialise iterating a cluster
void clustInitIterate(SDWORD clusterID)
{
SDWORD player, cluster;
iterateClusterID = clusterID;
cluster = aClusterMap[clusterID];
player = aClusterInfo[cluster] & CLUSTER_PLAYER_MASK;
if (aClusterInfo[cluster] & CLUSTER_DROID)
{
psIterateList = (BASE_OBJECT *)apsDroidLists[player];
}
else // if (aClusterInfo[cluster] & CLUSTER_STRUCTURE)
{
psIterateList = (BASE_OBJECT *)apsStructLists[player];
}
psIterateObj = NULL;
}
// iterate a cluster
BASE_OBJECT *clustIterate(void)
{
BASE_OBJECT *psStart;
SDWORD cluster;
cluster = aClusterMap[iterateClusterID];
if (psIterateObj == NULL)
{
psStart = psIterateList;
}
else
{
psStart = psIterateObj->psNext;
}
for(psIterateObj=psStart;
psIterateObj && psIterateObj->cluster != cluster;
psIterateObj = psIterateObj->psNext)
;
if (psIterateObj == NULL)
{
psIterateList = NULL;
}
return psIterateObj;
}
// find the center of a cluster
void clustGetCenter(BASE_OBJECT *psObj, SDWORD *px, SDWORD *py)
{
BASE_OBJECT *psList;
BASE_OBJECT *psCurr;
SDWORD averagex, averagey, num;
switch (psObj->type)
{
case OBJ_DROID:
psList = (BASE_OBJECT *)apsDroidLists[psObj->player];
break;
case OBJ_STRUCTURE:
psList = (BASE_OBJECT *)apsStructLists[psObj->player];
break;
default:
ASSERT(!"invalid object type", "clustGetCenter: invalid object type");
psList = NULL;
break;
}
averagex = 0;
averagey = 0;
num = 0;
for (psCurr = psList; psCurr; psCurr=psCurr->psNext)
{
if (psCurr->cluster == psObj->cluster)
{
averagex += (SDWORD)psCurr->pos.x;
averagey += (SDWORD)psCurr->pos.y;
num += 1;
}
}
if (num > 0)
{
*px = averagex / num;
*py = averagey / num;
}
else
{
*px = (SDWORD)psObj->pos.x;
*py = (SDWORD)psObj->pos.y;
}
}
// 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) &&
psObj->visible[player] &&
!(aClusterVisibility[psObj->cluster] & (1 << player)))
{
// DBPRINTF(("cluster %d (player %d) seen by player %d\n",
// clustGetClusterID(psObj), psObj->player, player));
aClusterVisibility[psObj->cluster] |= 1 << player;
psScrCBObjSeen = psObj;
psScrCBObjViewer = psViewer;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_OBJ_SEEN);
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;
}
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);
}
}