warzone2100/src/intdisplay.cpp

2491 lines
63 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
*/
/*
* IntDisplay.c
*
* Callback and display functions for interface.
*
*/
#include "lib/framework/frame.h"
#include "lib/framework/strres.h"
#include "lib/framework/math_ext.h"
/* Includes direct access to render library */
#include "lib/ivis_opengl/ivisdef.h"
#include "lib/ivis_opengl/piestate.h"
#include "lib/ivis_opengl/piepalette.h"
#include "lib/ivis_opengl/piemode.h" // ffs
#include "lib/ivis_opengl/pieclip.h" // ffs
#include "lib/ivis_opengl/pieblitfunc.h"
// FIXME Direct iVis implementation include!
#include "lib/ivis_opengl/bitimage.h"
#include "lib/ivis_opengl/piematrix.h"
#include "lib/framework/input.h"
#include "lib/widget/slider.h"
#include "lib/widget/editbox.h"
#include "lib/widget/button.h"
#include "lib/widget/label.h"
#include "lib/widget/bar.h"
#include "lib/gamelib/gtime.h"
#include "lib/sound/audio.h"
#include "intdisplay.h"
#include "objects.h"
#include "loop.h"
#include "map.h"
#include "radar.h"
#include "display3d.h"
#include "edit3d.h"
#include "structure.h"
#include "research.h"
#include "hci.h"
#include "stats.h"
#include "game.h"
#include "power.h"
#include "order.h"
#include "frontend.h"
#include "intimage.h"
#include "component.h"
#include "console.h"
#include "cmddroid.h"
#include "group.h"
#include "transporter.h"
#include "mission.h"
#include "multiplay.h"
// Is a button widget highlighted, either because the cursor is over it or it is flashing.
//
#define buttonIsHilite(p) ((p->getState() & WBUT_HIGHLIGHT) != 0)
#define FORM_OPEN_ANIM_DURATION (GAME_TICKS_PER_SEC/6) // Time duration for form open/close anims.
//the loop default value
#define DEFAULT_LOOP 1
static void StatGetResearchImage(BASE_STATS *psStat, Image *image, iIMDShape **Shape, BASE_STATS **ppGraphicData, bool drawTechIcon);
static int FormOpenAudioID; // ID of sfx to play when form opens.
static int FormCloseAudioID; // ID of sfx to play when form closes.
static int FormOpenCount; // Count used to ensure only one sfx played when two forms opening.
static int FormCloseCount; // Count used to ensure only one sfx played when two forms closeing.
#define DEFAULT_BUTTON_ROTATION (45)
static UDWORD ManuPower = 0; // Power required to manufacture the current item.
// Get the first factory assigned to a command droid
static STRUCTURE *droidGetCommandFactory(DROID *psDroid);
// Set audio IDs for form opening/closing anims.
// Use -1 to dissable audio.
//
void SetFormAudioIDs(int OpenID, int CloseID)
{
FormOpenAudioID = OpenID;
FormCloseAudioID = CloseID;
FormOpenCount = 0;
FormCloseCount = 0;
}
static void setBarGraphValue(W_BARGRAPH *barGraph, PIELIGHT colour, int value, int range)
{
barGraph->majorCol = colour;
barGraph->majorSize = PERNUM(WBAR_SCALE, clip(value, 0, range), range);
barGraph->show();
}
static void formatEmpty(W_BARGRAPH *barGraph)
{
barGraph->text.clear();
setBarGraphValue(barGraph, WZCOL_BLACK, 0, 1);
}
static void formatTimeText(W_BARGRAPH *barGraph, int time)
{
char timeText[20];
ssprintf(timeText, "%d:%02d", time / 60, time % 60);
barGraph->text = timeText;
barGraph->textCol = WZCOL_CONSTRUCTION_BARTEXT;
}
static void formatTime(W_BARGRAPH *barGraph, int buildPointsDone, int buildPointsTotal, int buildRate, char const *toolTip)
{
barGraph->setTip(toolTip);
if (buildRate != 0)
{
int timeToBuild = (buildPointsTotal - buildPointsDone) / buildRate;
formatTimeText(barGraph, timeToBuild);
}
else
{
barGraph->text.clear();
}
setBarGraphValue(barGraph, WZCOL_YELLOW, buildPointsDone, buildPointsTotal);
}
static void formatPowerText(W_BARGRAPH *barGraph, int neededPower)
{
char powerText[20];
ssprintf(powerText, "%d", neededPower);
barGraph->text = powerText;
barGraph->textCol = WZCOL_POWERQUEUE_BARTEXT;
}
static void formatPower(W_BARGRAPH *barGraph, int neededPower, int powerToBuild)
{
if (neededPower == -1 || powerToBuild == 0)
{
formatEmpty(barGraph);
return;
}
barGraph->setTip(_("Waiting for Power"));
formatPowerText(barGraph, neededPower);
setBarGraphValue(barGraph, WZCOL_GREEN, powerToBuild - neededPower, powerToBuild);
}
// Widget callback to update the progress bar in the object stats screen.
//
void intUpdateProgressBar(WIDGET *psWidget, W_CONTEXT *psContext)
{
BASE_OBJECT *psObj;
DROID *Droid;
STRUCTURE *Structure;
FACTORY *Manufacture;
RESEARCH_FACILITY *Research;
W_BARGRAPH *BarGraph = (W_BARGRAPH *)psWidget;
psObj = (BASE_OBJECT *)BarGraph->pUserData; // Get the object associated with this widget.
if (psObj == NULL)
{
BarGraph->hide();
return;
}
if (isDead((BASE_OBJECT *)psObj))
{
return;
}
BarGraph->majorSize = 0;
BarGraph->hide();
switch (psObj->type)
{
case OBJ_DROID: // If it's a droid and...
Droid = (DROID *)psObj;
if (DroidIsBuilding(Droid)) // Is it a building.
{
ASSERT(Droid->asBits[COMP_CONSTRUCT], "Invalid droid type");
Structure = DroidGetBuildStructure(Droid); // Get the structure's building.
if (Structure)
{
//show progress of build
if (Structure->currentBuildPts != 0)
{
formatTime(BarGraph, Structure->currentBuildPts, Structure->pStructureType->buildPoints, Structure->lastBuildRate, _("Build Progress"));
}
else
{
formatPower(BarGraph, checkPowerRequest(Structure), Structure->pStructureType->powerToBuild);
}
}
}
break;
case OBJ_STRUCTURE: // If it's a structure and...
Structure = (STRUCTURE *)psObj;
if (StructureIsManufacturingPending(Structure)) // Is it manufacturing.
{
Manufacture = StructureGetFactory(Structure);
if (Manufacture->psSubject != NULL && Manufacture->buildPointsRemaining < calcTemplateBuild(Manufacture->psSubject))
{
// Started production. Set the colour of the bar to yellow.
int buildPointsTotal = calcTemplateBuild(FactoryGetTemplate(Manufacture));
int buildRate = Manufacture->timeStartHold == 0 ? getBuildingProductionPoints(Structure) : 0;
formatTime(BarGraph, buildPointsTotal - Manufacture->buildPointsRemaining, buildPointsTotal, buildRate, _("Construction Progress"));
}
else
{
// Not yet started production.
int neededPower = checkPowerRequest(Structure);
int powerToBuild = Manufacture->psSubject ? calcTemplatePower(Manufacture->psSubject) : 0;
formatPower(BarGraph, neededPower, powerToBuild);
}
}
else if (structureIsResearchingPending(Structure)) // Is it researching.
{
Research = StructureGetResearch(Structure);
unsigned currentPoints = 0;
if (Research->psSubject != NULL)
{
currentPoints = asPlayerResList[selectedPlayer][Research->psSubject->index].currentPoints;
}
if (currentPoints != 0)
{
int researchRate = Research->timeStartHold == 0 ? getBuildingResearchPoints(Structure) : 0;
formatTime(BarGraph, currentPoints, Research->psSubject->researchPoints, researchRate, _("Research Progress"));
}
else
{
// Not yet started production.
int neededPower = checkPowerRequest(Structure);
int powerToBuild = Research->psSubject != NULL ? Research->psSubject->researchPower : 0;
formatPower(BarGraph, neededPower, powerToBuild);
}
}
break;
default:
ASSERT(false, "Invalid object type");
}
}
void intUpdateQuantity(WIDGET *psWidget, W_CONTEXT *psContext)
{
BASE_OBJECT *psObj;
STRUCTURE *Structure;
DROID_TEMPLATE *psTemplate;
W_LABEL *Label = (W_LABEL *)psWidget;
psObj = (BASE_OBJECT *)Label->pUserData; // Get the object associated with this widget.
Structure = (STRUCTURE *)psObj;
if (psObj != NULL && psObj->type == OBJ_STRUCTURE && StructureIsManufacturingPending(Structure))
{
ASSERT(!isDead(psObj), "Object is dead");
psTemplate = FactoryGetTemplate(StructureGetFactory(Structure));
int remaining = getProduction(Structure, psTemplate).numRemaining();
char tmp[20];
ssprintf(tmp, "%d", remaining);
Label->aText = QString::fromUtf8(tmp);
Label->show();
}
else
{
Label->hide();
}
}
//callback to display the factory number
void intAddFactoryInc(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
BASE_OBJECT *psObj = (BASE_OBJECT *)Label->pUserData;
// Get the object associated with this widget.
if (psObj != NULL && !isDead(psObj))
{
STRUCTURE *Structure = (STRUCTURE *)psObj;
FACTORY *Factory = &Structure->pFunctionality->factory;
ASSERT((Structure->pStructureType->type == REF_FACTORY ||
Structure->pStructureType->type == REF_CYBORG_FACTORY ||
Structure->pStructureType->type == REF_VTOL_FACTORY),
"Structure is not a factory");
char tmp[20];
ssprintf(tmp, "%u", Factory->psAssemblyPoint->factoryInc + 1);
Label->aText = QString::fromUtf8(tmp);
Label->show();
}
else
{
Label->aText.clear();
Label->hide();
}
}
//callback to display the production quantity number for a template
void intAddProdQuantity(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
DROID_TEMPLATE *psTemplate = (DROID_TEMPLATE *)Label->pUserData;
// Get the object associated with this widget.
if (psTemplate != NULL)
{
STRUCTURE *psStructure = NULL;
BASE_OBJECT *psObj = getCurrentSelected();
if (psObj != NULL && psObj->type == OBJ_STRUCTURE && !isDead(psObj))
{
psStructure = (STRUCTURE *)psObj;
}
ProductionRunEntry entry;
if (psStructure != NULL && StructIsFactory(psStructure))
{
entry = getProduction(psStructure, psTemplate);
}
// now find out how many we have built
if (entry.isValid())
{
char tmp[40];
if (psStructure->pFunctionality->factory.productionLoops != 0)
{
ssprintf(tmp, "%u/%u", entry.numRemaining(), entry.quantity);
}
else
{
ssprintf(tmp, "%u", entry.numRemaining());
}
Label->aText = QString::fromUtf8(tmp);
Label->show();
}
else
{
Label->aText.clear();
Label->hide();
}
}
}
//callback to display the production loop quantity number for a factory
void intAddLoopQuantity(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
STRUCTURE *psStruct = (STRUCTURE *)Label->pUserData;
//loop depends on the factory
if (psStruct && psStruct->pFunctionality && !psStruct->died)
{
FACTORY *psFactory = &psStruct->pFunctionality->factory;
if (psFactory->productionLoops == INFINITE_PRODUCTION)
{
Label->aText = QString::fromUtf8("");
}
else if (psFactory->productionLoops != 0)
{
char tmp[20];
ssprintf(tmp, "%u", psFactory->productionLoops + DEFAULT_LOOP);
Label->aText = QString::fromUtf8(tmp);
}
else
{
Label->aText.clear(); // Don't show "1" loop.
}
Label->show();
}
else
{
//hide the label if no factory
Label->aText.clear();
Label->hide();
}
}
// callback to update the command droid size label
void intUpdateCommandSize(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
BASE_OBJECT *psObj = (BASE_OBJECT *)Label->pUserData;
// Get the object associated with this widget.
if (psObj != NULL && !isDead(psObj))
{
DROID *psDroid = (DROID *)psObj;
ASSERT(psDroid->droidType == DROID_COMMAND,
"Droid is not a command droid");
char tmp[40];
ssprintf(tmp, "%u/%u", psDroid->psGroup ? psDroid->psGroup->getNumMembers() : 0, cmdDroidMaxGroup(psDroid));
Label->aText = QString::fromUtf8(tmp);
Label->show();
}
else
{
Label->aText.clear();
Label->hide();
}
}
// callback to update the command droid experience
void intUpdateCommandExp(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
BASE_OBJECT *psObj = (BASE_OBJECT *)Label->pUserData;
// Get the object associated with this widget.
if (psObj != NULL && !isDead(psObj))
{
DROID *psDroid = (DROID *)psObj;
ASSERT(psObj->type == OBJ_DROID, "Invalid droid pointer");
ASSERT(psDroid->droidType == DROID_COMMAND, "Droid is not a command droid");
int numStars = std::max((int)getDroidLevel(psDroid) - 1, 0);
Label->aText = QString(numStars, '*');
Label->show();
}
else
{
Label->aText.clear();
Label->hide();
}
}
// callback to update the command droid factories
void intUpdateCommandFact(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_LABEL *Label = (W_LABEL *)psWidget;
BASE_OBJECT *psObj = (BASE_OBJECT *)Label->pUserData;
SDWORD i, start;
// Get the object associated with this widget.
if (psObj != NULL && !isDead(psObj))
{
DROID *psDroid = (DROID *)psObj;
ASSERT(psObj->type == OBJ_DROID, "Invalid droid pointer");
ASSERT(psDroid->droidType == DROID_COMMAND, "Droid is not a command droid");
// see which type of factory this is for
if (Label->id >= IDOBJ_COUNTSTART && Label->id < IDOBJ_COUNTEND)
{
start = DSS_ASSPROD_SHIFT;
}
else if (Label->id >= IDOBJ_CMDFACSTART && Label->id < IDOBJ_CMDFACEND)
{
start = DSS_ASSPROD_CYBORG_SHIFT;
}
else
{
start = DSS_ASSPROD_VTOL_SHIFT;
}
Label->aText.clear();
for (i = 0; i < 5; ++i) // TODO Support up to MAX_FACTORY (which won't fit in the ugly secondaryOrder bitmask hack).
{
if (psDroid->secondaryOrder & (1 << (i + start)))
{
Label->aText.append((char)('0' + i + 1));
}
}
Label->show();
}
else
{
Label->aText.clear();
Label->hide();
}
}
// Widget callback to update and display the power bar.
// !!!!!!!!!!!!!!!!!!!!!!ONLY WORKS ON A SIDEWAYS POWERBAR!!!!!!!!!!!!!!!!!
void intDisplayPowerBar(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_BARGRAPH *BarGraph = (W_BARGRAPH *)psWidget;
SDWORD Avail, ManPow, realPower;
SDWORD Empty;
SDWORD BarWidth, textWidth = 0;
SDWORD iX, iY;
static char szVal[8];
double desiredPower = getPowerMinusQueued(selectedPlayer);
static double displayPower;
static unsigned lastRealTime;
displayPower = desiredPower + (displayPower - desiredPower) * exp((realTime - lastRealTime) / -80.); // If realTime < lastRealTime, then exp() returns 0 due to unsigned overflow.
lastRealTime = realTime;
ManPow = ManuPower / POWERBAR_SCALE;
Avail = (displayPower + 1e-8) / POWERBAR_SCALE;
realPower = (displayPower + 1e-8) - ManuPower;
BarWidth = BarGraph->width();
iV_SetFont(font_regular);
sprintf(szVal, "%d", realPower);
textWidth = iV_GetTextWidth(szVal);
BarWidth -= textWidth;
if (ManPow > Avail)
{
Empty = BarWidth - ManPow;
}
else
{
Empty = BarWidth - Avail;
}
if (Avail > BarWidth)
{
ManPow = PERNUM(BarWidth, ManPow, Avail);
Avail = BarWidth;
Empty = 0;
}
if (ManPow > BarWidth)
{
ManPow = BarWidth;
Avail = 0;
Empty = 0;
}
int x0 = xOffset + BarGraph->x();
int y0 = yOffset + BarGraph->y();
pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_ON);
pie_SetFogStatus(false);
iV_DrawImage(IntImages, IMAGE_PBAR_TOP, x0, y0);
iX = x0 + 3;
iY = y0 + 10;
x0 += iV_GetImageWidth(IntImages, IMAGE_PBAR_TOP);
//fill in the empty section behind text
if (textWidth > 0)
{
iV_DrawImageRepeatX(IntImages, IMAGE_PBAR_EMPTY, x0, y0, textWidth);
x0 += textWidth;
}
//draw required section
if (ManPow > Avail)
{
//draw the required in red
iV_DrawImageRepeatX(IntImages, IMAGE_PBAR_USED, x0, y0, ManPow);
}
else
{
iV_DrawImageRepeatX(IntImages, IMAGE_PBAR_REQUIRED, x0, y0, ManPow);
}
x0 += ManPow;
//draw the available section if any!
if (Avail - ManPow > 0)
{
iV_DrawImageRepeatX(IntImages, IMAGE_PBAR_AVAIL, x0, y0, Avail - ManPow);
x0 += Avail - ManPow;
}
//fill in the rest with empty section
if (Empty > 0)
{
iV_DrawImageRepeatX(IntImages, IMAGE_PBAR_EMPTY, x0, y0, Empty);
x0 += Empty;
}
iV_DrawImage(IntImages, IMAGE_PBAR_BOTTOM, x0, y0);
if (Avail < 0)
{
const char *need = _("Need more resources!");
if ((realTime / 1250) % 5 == 0)
{
iV_SetTextColour(WZCOL_BLACK);
iV_SetFont(font_small);
iV_DrawText(need, iX + 102, iY - 1);
iV_SetTextColour(WZCOL_RED);
}
else
{
iV_SetTextColour(WZCOL_RED);
iV_SetFont(font_small);
iV_DrawText(need, iX + 102, iY - 1);
}
}
else
{
iV_SetTextColour(WZCOL_TEXT_BRIGHT);
}
// draw text value
iV_SetFont(font_regular);
iV_DrawText(szVal, iX, iY);
}
IntFancyButton::IntFancyButton(WIDGET *parent)
: W_CLICKFORM(parent)
, imdRotation(DEFAULT_BUTTON_ROTATION)
, imdRotationRate(0)
, buttonType(TOPBUTTON)
{}
void IntFancyButton::doRotation()
{
imdRotationRate += realTimeAdjustedAverage(isHighlighted()? 2*BUTTONOBJ_ROTSPEED : -4*BUTTONOBJ_ROTSPEED);
imdRotationRate = clip(imdRotationRate, 0, BUTTONOBJ_ROTSPEED);
imdRotation += realTimeAdjustedAverage(imdRotationRate);
}
void IntFancyButton::displayIfHighlight(int xOffset, int yOffset)
{
if (isHighlighted())
{
iV_DrawImage(IntImages, buttonType == TOPBUTTON? IMAGE_BUT_HILITE : IMAGE_BUTB_HILITE, xOffset + x(), yOffset + y());
}
}
IntStatusButton::IntStatusButton(WIDGET *parent)
: IntObjectButton(parent)
, theStats(nullptr)
{
buttonType = TOPBUTTON;
}
// Widget callback to display a rendered status button, ie the progress of a manufacturing or building task.
void IntStatusButton::display(int xOffset, int yOffset)
{
STRUCTURE *Structure;
DROID *Droid;
BASE_STATS *Stats, *psResGraphic;
UDWORD compID;
bool bOnHold = false;
doRotation();
ImdObject object;
Image image;
if (psObj && isDead(psObj))
{
// this may catch this horrible crash bug we've been having,
// who knows?.... Shipping tomorrow, la de da :-)
psObj = NULL;
intRefreshScreen();
}
if (psObj)
{
switch (psObj->type)
{
case OBJ_DROID: // If it's a droid...
Droid = (DROID *)psObj;
if (DroidIsBuilding(Droid))
{
Structure = DroidGetBuildStructure(Droid);
if (Structure)
{
object = ImdObject::Structure(Structure);
}
}
else if (DroidGoingToBuild(Droid))
{
Stats = DroidGetBuildStats(Droid);
ASSERT(Stats != NULL, "NULL Stats pointer.");
object = ImdObject::StructureStat(Stats);
}
else if (orderState(Droid, DORDER_DEMOLISH))
{
Stats = structGetDemolishStat();
ASSERT(Stats != NULL, "NULL Stats pointer.");
object = ImdObject::StructureStat(Stats);
}
else if (Droid->droidType == DROID_COMMAND)
{
Structure = droidGetCommandFactory(Droid);
if (Structure)
{
object = ImdObject::Structure(Structure);
}
}
break;
case OBJ_STRUCTURE: // If it's a structure...
Structure = (STRUCTURE *)psObj;
switch (Structure->pStructureType->type)
{
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
if (StructureIsManufacturingPending(Structure))
{
object = ImdObject::DroidTemplate(FactoryGetTemplate(StructureGetFactory(Structure)));
bOnHold = StructureIsOnHoldPending(Structure);
}
break;
case REF_RESEARCH:
if (structureIsResearchingPending(Structure))
{
iIMDShape *shape;
Stats = theStats;
if (!Stats)
{
break;
}
bOnHold = StructureIsOnHoldPending(Structure);
StatGetResearchImage(Stats, &image, &shape, &psResGraphic, false);
if (psResGraphic)
{
// we have a Stat associated with this research topic
if (StatIsStructure(psResGraphic))
{
// overwrite the Object pointer
object = ImdObject::StructureStat(psResGraphic);
}
else
{
compID = StatIsComponent(psResGraphic);
if (compID != COMP_NUMCOMPONENTS)
{
// overwrite the Object pointer
object = ImdObject::Component(psResGraphic);
}
else
{
ASSERT(false, "Invalid Stat for research button");
object = ImdObject::Research(nullptr);
}
}
}
else
{
// no Stat for this research topic so just use the graphic provided
// if Object != NULL the there must be a IMD so set the object to
// equal the Research stat
if (shape != nullptr)
{
object = ImdObject::Research(Stats);
}
}
}
break;
default:
break;
}
break;
default:
ASSERT(false, "Invalid structure type");
}
}
// Render the object into the button.
displayIMD(image, object, xOffset, yOffset);
//need to flash the button if a factory is on hold production
if (bOnHold)
{
iV_DrawImage(IntImages, ((realTime / 250) % 2) == 0? IMAGE_BUT0_DOWN : IMAGE_BUT_HILITE, xOffset + x(), yOffset + y());
}
else
{
displayIfHighlight(xOffset, yOffset);
}
}
IntObjectButton::IntObjectButton(WIDGET *parent)
: IntFancyButton(parent)
, psObj(nullptr)
{
buttonType = BTMBUTTON;
}
// Widget callback to display a rendered object button.
void IntObjectButton::display(int xOffset, int yOffset)
{
doRotation();
ImdObject object;
if (psObj && isDead(psObj))
{
// this may catch this horrible crash bug we've been having,
// who knows?.... Shipping tomorrow, la de da :-)
psObj = NULL;
intRefreshScreen();
}
if (psObj)
{
switch (psObj->type)
{
case OBJ_DROID: // If it's a droid...
object = ImdObject::Droid(psObj);
break;
case OBJ_STRUCTURE: // If it's a structure...
object = ImdObject::Structure(psObj);
break;
default:
ASSERT(false, "Invalid structure type");
}
}
displayIMD(Image(), object, xOffset, yOffset);
displayIfHighlight(xOffset, yOffset);
}
IntStatsButton::IntStatsButton(WIDGET *parent)
: IntFancyButton(parent)
, Stat(nullptr)
{}
// Widget callback to display a rendered stats button, ie the job selection window buttons.
//
void IntStatsButton::display(int xOffset, int yOffset)
{
BASE_STATS * psResGraphic;
SDWORD compID;
doRotation();
ImdObject object;
Image image;
if (Stat)
{
if (StatIsStructure(Stat))
{
object = ImdObject::StructureStat(Stat);
}
else if (StatIsTemplate(Stat))
{
object = ImdObject::DroidTemplate(Stat);
}
else if (StatIsFeature(Stat))
{
object = ImdObject::Feature(Stat);
}
else
{
compID = StatIsComponent(Stat); // This failes for viper body.
if (compID != COMP_NUMCOMPONENTS)
{
object = ImdObject::Component(Stat);
}
else if (StatIsResearch(Stat))
{
iIMDShape *shape;
StatGetResearchImage(Stat, &image, &shape, &psResGraphic, true);
if (psResGraphic)
{
//we have a Stat associated with this research topic
if (StatIsStructure(psResGraphic))
{
object = ImdObject::StructureStat(psResGraphic);
}
else
{
compID = StatIsComponent(psResGraphic);
if (compID != COMP_NUMCOMPONENTS)
{
//overwrite the Object pointer
object = ImdObject::Component(psResGraphic);
}
else
{
ASSERT(false, "Invalid Stat for research button");
object = ImdObject::Research(nullptr);
}
}
}
else
{
//no Stat for this research topic so just use the graphic provided
//if Object != NULL the there must be a IMD so set the object to
//equal the Research stat
if (shape != nullptr)
{
object = ImdObject::Research(Stat);
}
}
}
}
}
else
{
//BLANK button for now - AB 9/1/98
object = ImdObject::Component(nullptr);
}
displayIMD(image, object, xOffset, yOffset);
displayIfHighlight(xOffset, yOffset);
}
IntFormAnimated::IntFormAnimated(WIDGET *parent, bool openAnimate)
: W_FORM(parent)
, startTime(0)
, currentAction(openAnimate? 0 : 2)
{
disableChildren = openAnimate;
}
void IntFormAnimated::closeAnimateDelete()
{
currentAction = 3;
disableChildren = true;
}
void IntFormAnimated::display(int xOffset, int yOffset)
{
QRect aOpen(xOffset + x(), yOffset + y(), width(), height());
QRect aClosed(aOpen.x() + aOpen.width()/4, aOpen.y() + aOpen.height()/2 - 4, aOpen.width()/2, 8);
QRect aBegin;
QRect aEnd;
switch (currentAction)
{
case 1: FormOpenCount = 0; break;
case 4: FormCloseCount = 0; break;
}
switch (currentAction)
{
case 0: // Start opening.
if (FormOpenAudioID >= 0 && FormOpenCount == 0)
{
audio_PlayTrack(FormOpenAudioID);
++FormOpenCount;
}
startTime = realTime;
++currentAction;
// No break.
case 1: // Continue opening.
aBegin = aClosed;
aEnd = aOpen;
break;
case 2: // Open.
aBegin = aOpen;
aEnd = aOpen;
startTime = realTime;
break;
case 3: // Start closing.
if (FormCloseAudioID >= 0 && FormCloseCount == 0)
{
audio_PlayTrack(FormCloseAudioID);
FormCloseCount++;
}
startTime = realTime;
++currentAction;
// No break.
case 4: // Continue closing.
aBegin = aOpen;
aEnd = aClosed;
break;
}
int den = FORM_OPEN_ANIM_DURATION;
int num = std::min<unsigned>(realTime - startTime, den);
if (num == den)
{
++currentAction;
switch (currentAction)
{
case 2: disableChildren = false; break;
case 5: deleteLater(); break;
}
}
QRect aCur = QRect(aBegin.x() + (aEnd.x() - aBegin.x()) *num/den,
aBegin.y() + (aEnd.y() - aBegin.y()) *num/den,
aBegin.width() + (aEnd.width() - aBegin.width()) *num/den,
aBegin.height() + (aEnd.height() - aBegin.height())*num/den);
RenderWindowFrame(FRAME_NORMAL, aCur.x(), aCur.y(), aCur.width(), aCur.height());
}
// Display an image for a widget.
//
void intDisplayImage(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
iV_DrawImage(IntImages, psWidget->UserData, x, y);
}
//draws the mission clock - flashes when below a predefined time
void intDisplayMissionClock(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
// Draw the background image
iV_DrawImage(IntImages, UNPACKDWORD_TRI_B(psWidget->UserData), x, y);
// Need to flash the timer when < 5 minutes remaining, but > 4 minutes
bool flash = UNPACKDWORD_TRI_A(psWidget->UserData);
if (flash && ((realTime / 250) % 2) == 0)
{
iV_DrawImage(IntImages, UNPACKDWORD_TRI_C(psWidget->UserData), x, y);
}
}
// Display one of two images depending on if the widget is hilighted by the mouse.
//
void intDisplayImageHilight(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
UWORD ImageID;
bool Hilight = false;
switch (psWidget->type)
{
case WIDG_FORM:
Hilight = (psWidget->getState() & WBUT_HIGHLIGHT) != 0;
break;
case WIDG_BUTTON:
Hilight = buttonIsHilite(psWidget);
break;
case WIDG_EDITBOX:
if (((W_EDITBOX *)psWidget)->state & WEDBS_HILITE)
{
Hilight = true;
}
break;
case WIDG_SLIDER:
if (((W_SLIDER *)psWidget)->state & SLD_HILITE)
{
Hilight = true;
}
break;
default:
Hilight = false;
}
ImageID = UNPACKDWORD_TRI_C(psWidget->UserData);
//need to flash the button if Full Transporter
bool flash = UNPACKDWORD_TRI_A(psWidget->UserData);
if (flash && psWidget->id == IDTRANS_LAUNCH)
{
if (((realTime / 250) % 2) == 0)
{
iV_DrawImage(IntImages, UNPACKDWORD_TRI_B(psWidget->UserData), x, y);
}
else
{
iV_DrawImage(IntImages, ImageID, x, y);
}
}
else
{
iV_DrawImage(IntImages, ImageID, x, y);
if (Hilight)
{
iV_DrawImage(IntImages, UNPACKDWORD_TRI_B(psWidget->UserData), x, y);
}
}
}
// Display one of two images depending on whether the widget is highlighted by the mouse.
void intDisplayButtonHilight(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
UWORD ImageID;
unsigned state = psWidget->getState();
bool grey = (state & WBUT_DISABLE) != 0;
bool down = (state & (WBUT_DOWN | WBUT_LOCK | WBUT_CLICKLOCK)) != 0;
bool highlight = (state & WBUT_HIGHLIGHT) != 0;
if (grey)
{
ImageID = UNPACKDWORD_TRI_A(psWidget->UserData);
highlight = false;
}
else
{
ImageID = UNPACKDWORD_TRI_C(psWidget->UserData) + down;
}
iV_DrawImage(IntImages, ImageID, x, y);
if (highlight)
{
iV_DrawImage(IntImages, UNPACKDWORD_TRI_B(psWidget->UserData), x, y);
}
}
// Flash one of two images, regardless of whether or not it is highlighted
// Commented-out portions are retained because I am planning on making the intensity of the
// flash depend on whether or not the button is highlighted.
void intDisplayButtonFlash(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
UWORD ImageID;
ASSERT(psWidget->type == WIDG_BUTTON, "Not a button");
if ((realTime / 250) % 2 == 0)
{
ImageID = UNPACKDWORD_TRI_B(psWidget->UserData);
}
else
{
ImageID = UNPACKDWORD_TRI_C(psWidget->UserData);
}
iV_DrawImage(IntImages, ImageID, x, y);
}
// Display one of three images depending on if the widget is currently depressed (ah!).
//
void intDisplayButtonPressed(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_BUTTON *psButton = (W_BUTTON *)psWidget;
UDWORD x = xOffset + psButton->x();
UDWORD y = yOffset + psButton->y();
UBYTE Hilight = 0;
UWORD ImageID;
if (psButton->state & (WBUT_DOWN | WBUT_LOCK | WBUT_CLICKLOCK))
{
ImageID = UNPACKDWORD_TRI_A(psWidget->UserData);
}
else
{
ImageID = UNPACKDWORD_TRI_C(psWidget->UserData);
}
Hilight = (UBYTE)buttonIsHilite(psButton);
iV_DrawImage(IntImages, ImageID, x, y);
if (Hilight)
{
iV_DrawImage(IntImages, UNPACKDWORD_TRI_B(psWidget->UserData), x, y);
}
}
void intDisplaySlider(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_SLIDER *Slider = (W_SLIDER *)psWidget;
int x = xOffset + psWidget->x();
int y = yOffset + psWidget->y();
iV_DrawImage(IntImages, IMAGE_SLIDER_BACK, x + STAT_SLD_OX, y + STAT_SLD_OY);
int sx = (Slider->width() - Slider->barSize) * Slider->pos / Slider->numStops;
iV_DrawImage(IntImages, IMAGE_SLIDER_BUT, x + sx, y - 2);
}
/* display highlighted edit box from left, middle and end edit box graphics */
void intDisplayEditBox(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_EDITBOX *psEditBox = (W_EDITBOX *) psWidget;
UWORD iImageIDLeft, iImageIDMid, iImageIDRight;
UDWORD iX, iY, iDX, iXRight;
UDWORD iXLeft = xOffset + psWidget->x(),
iYLeft = yOffset + psWidget->y();
if (psEditBox->state & WEDBS_HILITE)
{
iImageIDLeft = IMAGE_DES_EDITBOXLEFTH;
iImageIDMid = IMAGE_DES_EDITBOXMIDH;
iImageIDRight = IMAGE_DES_EDITBOXRIGHTH;
}
else
{
iImageIDLeft = IMAGE_DES_EDITBOXLEFT;
iImageIDMid = IMAGE_DES_EDITBOXMID;
iImageIDRight = IMAGE_DES_EDITBOXRIGHT;
}
/* draw left side of bar */
iX = iXLeft;
iY = iYLeft;
iV_DrawImage(IntImages, iImageIDLeft, iX, iY);
/* draw middle of bar */
iX += iV_GetImageWidth(IntImages, iImageIDLeft);
iDX = iV_GetImageWidth(IntImages, iImageIDMid);
iXRight = xOffset + psWidget->width() - iV_GetImageWidth(IntImages, iImageIDRight);
while (iX < iXRight)
{
iV_DrawImage(IntImages, iImageIDMid, iX, iY);
iX += iDX;
}
/* draw right side of bar */
iV_DrawImage(IntImages, iImageIDRight, iXRight, iY);
}
void intDisplayNumber(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_LABEL *Label = (W_LABEL *)psWidget;
UDWORD x = Label->x() + xOffset;
UDWORD y = Label->y() + yOffset;
UDWORD Quantity = 1;
//Quantity depends on the factory
if (Label->pUserData != NULL)
{
STRUCTURE *psStruct = (STRUCTURE *)Label->pUserData;
FACTORY *psFactory = (FACTORY *)psStruct->pFunctionality;
if (psFactory && !psStruct->died)
{
Quantity = psFactory->productionLoops;
}
}
if (Quantity >= STAT_SLDSTOPS)
{
iV_DrawImage(IntImages, IMAGE_SLIDER_INFINITY, x + 4, y);
}
else
{
char tmp[20];
ssprintf(tmp, "%02u", Quantity);
Label->aText = QString::fromUtf8(tmp);
for (int i = 0; i < Label->aText.size(); ++i)
{
iV_DrawImage(IntImages, (UWORD)(IMAGE_0 + (Label->aText.toUtf8()[i] - '0')), x, y);
x += iV_GetImageWidth(IntImages, (UWORD)(IMAGE_0 + (Label->aText.toUtf8()[i] - '0'))) + 1;
}
}
}
// Initialise all the surfaces,graphics etc. used by the interface.
//
void intInitialiseGraphics(void)
{
// Initialise any bitmaps used by the interface.
imageInitBitmaps();
}
// Clear a button bitmap. ( copy the button background ).
void IntFancyButton::displayClear(int xOffset, int yOffset)
{
if (isDown())
{
iV_DrawImage(IntImages, IMAGE_BUT0_DOWN + buttonType*2, xOffset + x(), yOffset + y());
}
else
{
iV_DrawImage(IntImages, IMAGE_BUT0_UP + buttonType*2, xOffset + x(), yOffset + y());
}
}
// Create a button by rendering an IMD object into it.
void IntFancyButton::displayIMD(Image image, ImdObject imdObject, int xOffset, int yOffset)
{
if (imdObject.empty())
{
displayImage(image, xOffset, yOffset);
return;
}
int ButXPos = xOffset + x();
int ButYPos = yOffset + y();
Vector3i Rotation, Position;
UDWORD ox, oy;
UDWORD Radius;
UDWORD basePlateSize;
SDWORD scale;
if (isDown())
{
ox = oy = 2;
}
else
{
ox = oy = 0;
}
ImdType IMDType = imdObject.type;
void *Object = imdObject.ptr;
if (IMDType == IMDTYPE_DROID || IMDType == IMDTYPE_DROIDTEMPLATE)
{
// The case where we have to render a composite droid.
if (isDown())
{
//the top button is smaller than the bottom button
if (buttonType == TOPBUTTON)
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUT0_DOWN) / 2) + 2,
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUT0_DOWN) / 2) + 2 + 8);
}
else
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUTB0_DOWN) / 2) + 2,
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUTB0_DOWN) / 2) + 2 + 12);
}
}
else
{
//the top button is smaller than the bottom button
if (buttonType == TOPBUTTON)
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUT0_UP) / 2),
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUT0_UP) / 2) + 8 );
}
else
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUT0_UP) / 2),
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUTB0_UP) / 2) + 12);
}
}
if (IMDType == IMDTYPE_DROID)
{
Radius = getComponentDroidRadius((DROID *)Object);
}
else
{
Radius = getComponentDroidTemplateRadius((DROID_TEMPLATE *)Object);
}
scale = DROID_BUT_SCALE;
ASSERT(Radius <= 128, "create PIE button big component found");
displayClear(xOffset, yOffset);
Rotation.x = -30;
Rotation.y = imdRotation;
Rotation.z = 0;
if (IMDType == IMDTYPE_DROID)
{
if (((DROID *)Object)->droidType == DROID_TRANSPORTER || ((DROID *)Object)->droidType == DROID_SUPERTRANSPORTER)
{
Position.x = 0;
Position.y = 0;
Position.z = BUTTON_DEPTH;
if (((DROID *)Object)->droidType == DROID_TRANSPORTER)
{
scale = DROID_BUT_SCALE / 2;
}
else
{
scale = DROID_BUT_SCALE / 3;
}
}
else
{
Position.x = Position.y = 0;
Position.z = BUTTON_DEPTH;
}
}
else//(IMDType == IMDTYPE_DROIDTEMPLATE)
{
if (((DROID_TEMPLATE *)Object)->droidType == DROID_TRANSPORTER || ((DROID_TEMPLATE *)Object)->droidType == DROID_SUPERTRANSPORTER)
{
Position.x = 0;
Position.y = 0;
Position.z = BUTTON_DEPTH;
if (((DROID_TEMPLATE *)Object)->droidType == DROID_TRANSPORTER)
{
scale = DROID_BUT_SCALE / 2;
}
else
{
scale = DROID_BUT_SCALE / 3;
}
}
else
{
Position.x = Position.y = 0;
Position.z = BUTTON_DEPTH;
}
}
//lefthand display droid buttons
if (IMDType == IMDTYPE_DROID)
{
displayComponentButtonObject((DROID *)Object, &Rotation, &Position, scale);
}
else
{
displayComponentButtonTemplate((DROID_TEMPLATE *)Object, &Rotation, &Position, scale);
}
}
else
{
// Just drawing a single IMD.
if (isDown())
{
if (buttonType == TOPBUTTON)
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUT0_DOWN) / 2) + 2,
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUT0_DOWN) / 2) + 2 + 8);
}
else
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUTB0_DOWN) / 2) + 2,
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUTB0_DOWN) / 2) + 2 + 12);
}
}
else
{
if (buttonType == TOPBUTTON)
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUT0_UP) / 2),
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUT0_UP) / 2) + 8 );
}
else
{
pie_SetGeometricOffset(
(ButXPos + iV_GetImageWidth(IntImages, IMAGE_BUTB0_UP) / 2),
(ButYPos + iV_GetImageHeight(IntImages, IMAGE_BUTB0_UP) / 2) + 12);
}
}
// Decide which button grid size to use.
if (IMDType == IMDTYPE_COMPONENT)
{
Radius = getComponentRadius((BASE_STATS *)Object);
scale = rescaleButtonObject(Radius, COMP_BUT_SCALE, COMPONENT_RADIUS);
// NOTE: The Super transport is huge, and is considered a component type, so refit it to inside the button.
BASE_STATS *psStats = (BASE_STATS *)Object;
if (psStats->id.compare("SuperTransportBody") == 0)
{
scale *= .4;
}
else if (psStats->id.compare("TransporterBody") == 0)
{
scale *= .6;
}
}
else if (IMDType == IMDTYPE_RESEARCH)
{
Radius = getResearchRadius((BASE_STATS *)Object);
if (Radius <= 100)
{
scale = rescaleButtonObject(Radius, COMP_BUT_SCALE, COMPONENT_RADIUS);
}
else if (Radius <= 128)
{
scale = SMALL_STRUCT_SCALE;
}
else if (Radius <= 256)
{
scale = MED_STRUCT_SCALE;
}
else
{
scale = LARGE_STRUCT_SCALE;
}
}
else if (IMDType == IMDTYPE_STRUCTURE)
{
basePlateSize = getStructureSizeMax((STRUCTURE *)Object);
if (basePlateSize == 1)
{
scale = SMALL_STRUCT_SCALE;
}
else if (basePlateSize == 2)
{
scale = MED_STRUCT_SCALE;
}
else
{
scale = LARGE_STRUCT_SCALE;
}
}
else if (IMDType == IMDTYPE_STRUCTURESTAT)
{
basePlateSize = getStructureStatSizeMax((STRUCTURE_STATS *)Object);
if (basePlateSize == 1)
{
scale = SMALL_STRUCT_SCALE;
}
else if (basePlateSize == 2)
{
scale = MED_STRUCT_SCALE;
}
else
{
scale = LARGE_STRUCT_SCALE;
}
}
else if (IMDType == IMDTYPE_FEATURE)
{
int imdRadius = ((iIMDShape *)Object)->radius;
if (imdRadius <= 40)
{
scale = ULTRA_SMALL_FEATURE_SCALE;
}
else if (imdRadius <= 64)
{
scale = REALLY_SMALL_FEATURE_SCALE;
}
else if (imdRadius <= 128)
{
scale = SMALL_FEATURE_SCALE;
}
else if (imdRadius <= 256)
{
scale = MED_FEATURE_SCALE;
}
else
{
scale = LARGE_FEATURE_SCALE;
}
}
else
{
Radius = ((iIMDShape *)Object)->sradius;
if (Radius <= 128)
{
scale = SMALL_STRUCT_SCALE;
}
else if (Radius <= 256)
{
scale = MED_STRUCT_SCALE;
}
else
{
scale = LARGE_STRUCT_SCALE;
}
}
displayClear(xOffset, yOffset);
Rotation.x = -30;
Rotation.y = imdRotation;
Rotation.z = 0;
Position.x = 0;
Position.y = 0;
Position.z = BUTTON_DEPTH; //was Position.z = Radius*30;
if (!image.isNull())
{
iV_DrawImage(image, ButXPos + ox, ButYPos + oy);
}
pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
/* all non droid buttons */
if (IMDType == IMDTYPE_COMPONENT)
{
displayComponentButton((BASE_STATS *)Object, &Rotation, &Position, scale);
}
else if (IMDType == IMDTYPE_RESEARCH)
{
displayResearchButton((BASE_STATS *)Object, &Rotation, &Position, scale);
}
else if (IMDType == IMDTYPE_STRUCTURE)
{
displayStructureButton((STRUCTURE *)Object, &Rotation, &Position, scale);
}
else if (IMDType == IMDTYPE_STRUCTURESTAT)
{
displayStructureStatButton((STRUCTURE_STATS *)Object, &Rotation, &Position, scale);
}
else if (IMDType == IMDTYPE_FEATURE)
{
displayIMDButton((iIMDShape *)Object, &Rotation, &Position, scale);
}
else
{
displayIMDButton((iIMDShape *)Object, &Rotation, &Position, scale);
}
pie_SetDepthBufferStatus(DEPTH_CMP_ALWAYS_WRT_ON);
}
}
// Create a button by rendering an image into it.
void IntFancyButton::displayImage(Image image, int xOffset, int yOffset)
{
if (image.isNull())
{
displayBlank(xOffset, yOffset);
return;
}
displayClear(xOffset, yOffset);
iV_DrawImage(image, xOffset + x(), yOffset + y());
}
// Create a blank button.
void IntFancyButton::displayBlank(int xOffset, int yOffset)
{
UDWORD ox, oy;
if (isDown())
{
ox = oy = 1;
}
else
{
ox = oy = 0;
}
displayClear(xOffset, yOffset);
// Draw a question mark, bit of quick hack this.
iV_DrawImage(IntImages, IMAGE_QUESTION_MARK, xOffset + x() + ox + 10, yOffset + y() + oy + 3);
}
// Returns true if the droid is currently building something.
//
bool DroidIsBuilding(DROID *Droid)
{
BASE_STATS *Stats;
UDWORD x, y;
if (!(droidType(Droid) == DROID_CONSTRUCT ||
droidType(Droid) == DROID_CYBORG_CONSTRUCT))
{
return false;
}
if (orderStateStatsLoc(Droid, DORDER_BUILD, &Stats, &x, &y))
{
// Moving to build location?
return false;
}
else if (orderStateObj(Droid, DORDER_BUILD)
|| orderStateObj(Droid, DORDER_HELPBUILD)) // Is building or helping?
{
return true;
}
return false;
}
// Returns true if the droid has been ordered build something ( but has'nt started yet )
//
bool DroidGoingToBuild(DROID *Droid)
{
BASE_STATS *Stats;
UDWORD x, y;
if (!(droidType(Droid) == DROID_CONSTRUCT ||
droidType(Droid) == DROID_CYBORG_CONSTRUCT))
{
return false;
}
if (orderStateStatsLoc(Droid, DORDER_BUILD, &Stats, &x, &y)) // Moving to build location?
{
return true;
}
return false;
}
// Get the structure for a structure which a droid is currently building.
//
STRUCTURE *DroidGetBuildStructure(DROID *Droid)
{
BASE_OBJECT *Structure = NULL;
if (orderStateObj(Droid, DORDER_BUILD))
{
Structure = orderStateObj(Droid, DORDER_HELPBUILD);
}
return (STRUCTURE *)Structure;
}
// Get the first factory assigned to a command droid
static STRUCTURE *droidGetCommandFactory(DROID *psDroid)
{
SDWORD inc;
STRUCTURE *psCurr;
for (inc = 0; inc < MAX_FACTORY; inc++)
{
if (psDroid->secondaryOrder & (1 << (inc + DSS_ASSPROD_SHIFT)))
{
// found an assigned factory - look for it in the lists
for (psCurr = apsStructLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
if ((psCurr->pStructureType->type == REF_FACTORY) &&
(((FACTORY *)psCurr->pFunctionality)->
psAssemblyPoint->factoryInc == inc))
{
return psCurr;
}
}
}
if (psDroid->secondaryOrder & (1 << (inc + DSS_ASSPROD_CYBORG_SHIFT)))
{
// found an assigned factory - look for it in the lists
for (psCurr = apsStructLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
if ((psCurr->pStructureType->type == REF_CYBORG_FACTORY) &&
(((FACTORY *)psCurr->pFunctionality)->
psAssemblyPoint->factoryInc == inc))
{
return psCurr;
}
}
}
if (psDroid->secondaryOrder & (1 << (inc + DSS_ASSPROD_VTOL_SHIFT)))
{
// found an assigned factory - look for it in the lists
for (psCurr = apsStructLists[psDroid->player]; psCurr; psCurr = psCurr->psNext)
{
if ((psCurr->pStructureType->type == REF_VTOL_FACTORY) &&
(((FACTORY *)psCurr->pFunctionality)->
psAssemblyPoint->factoryInc == inc))
{
return psCurr;
}
}
}
}
return NULL;
}
// Get the stats for a structure which a droid is going to ( but not yet ) building.
//
BASE_STATS *DroidGetBuildStats(DROID *Droid)
{
BASE_STATS *Stats;
UDWORD x, y;
if (orderStateStatsLoc(Droid, DORDER_BUILD, &Stats, &x, &y)) // Moving to build location?
{
return Stats;
}
return NULL;
}
iIMDShape *DroidGetIMD(DROID *Droid)
{
return Droid->sDisplay.imd;
}
template<typename Functionality>
static inline bool _structureIsManufacturingPending(Functionality const &functionality)
{
if (functionality.statusPending != FACTORY_NOTHING_PENDING)
{
return functionality.statusPending == FACTORY_START_PENDING || functionality.statusPending == FACTORY_HOLD_PENDING;
}
return functionality.psSubject != NULL;
}
bool StructureIsManufacturingPending(STRUCTURE *structure)
{
switch (structure->pStructureType->type)
{
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
return _structureIsManufacturingPending(structure->pFunctionality->factory);
default:
return false;
}
}
FACTORY *StructureGetFactory(STRUCTURE *Structure)
{
return &Structure->pFunctionality->factory;
}
bool structureIsResearchingPending(STRUCTURE *structure)
{
return structure->pStructureType->type == REF_RESEARCH && _structureIsManufacturingPending(structure->pFunctionality->researchFacility);
}
template<typename Functionality>
static inline bool structureIsOnHoldPending(Functionality const &functionality)
{
if (functionality.statusPending != FACTORY_NOTHING_PENDING)
{
return functionality.statusPending == FACTORY_HOLD_PENDING;
}
return functionality.timeStartHold != 0;
}
bool StructureIsOnHoldPending(STRUCTURE *structure)
{
switch (structure->pStructureType->type)
{
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
return structureIsOnHoldPending(structure->pFunctionality->factory);
case REF_RESEARCH:
return structureIsOnHoldPending(structure->pFunctionality->researchFacility);
default:
ASSERT(false, "Huh?");
return false;
}
}
RESEARCH_FACILITY *StructureGetResearch(STRUCTURE *Structure)
{
return &Structure->pFunctionality->researchFacility;
}
DROID_TEMPLATE *FactoryGetTemplate(FACTORY *Factory)
{
if (Factory->psSubjectPending != NULL)
{
return (DROID_TEMPLATE *)Factory->psSubjectPending;
}
return (DROID_TEMPLATE *)Factory->psSubject;
}
bool StatIsStructure(BASE_STATS const *Stat)
{
return (Stat->ref >= REF_STRUCTURE_START && Stat->ref <
REF_STRUCTURE_START + REF_RANGE);
}
bool StatIsFeature(BASE_STATS const *Stat)
{
return (Stat->ref >= REF_FEATURE_START && Stat->ref <
REF_FEATURE_START + REF_RANGE);
}
iIMDShape *StatGetStructureIMD(BASE_STATS *Stat, UDWORD Player)
{
(void)Player;
return ((STRUCTURE_STATS *)Stat)->pIMD[0];
}
bool StatIsTemplate(BASE_STATS *Stat)
{
return (Stat->ref >= REF_TEMPLATE_START &&
Stat->ref < REF_TEMPLATE_START + REF_RANGE);
}
SDWORD StatIsComponent(BASE_STATS *Stat)
{
if (Stat->ref >= REF_BODY_START &&
Stat->ref < REF_BODY_START + REF_RANGE)
{
return COMP_BODY;
}
if (Stat->ref >= REF_BRAIN_START &&
Stat->ref < REF_BRAIN_START + REF_RANGE)
{
return COMP_BRAIN;
}
if (Stat->ref >= REF_PROPULSION_START &&
Stat->ref < REF_PROPULSION_START + REF_RANGE)
{
return COMP_PROPULSION;
}
if (Stat->ref >= REF_WEAPON_START &&
Stat->ref < REF_WEAPON_START + REF_RANGE)
{
return COMP_WEAPON;
}
if (Stat->ref >= REF_SENSOR_START &&
Stat->ref < REF_SENSOR_START + REF_RANGE)
{
return COMP_SENSOR;
}
if (Stat->ref >= REF_ECM_START &&
Stat->ref < REF_ECM_START + REF_RANGE)
{
return COMP_ECM;
}
if (Stat->ref >= REF_CONSTRUCT_START &&
Stat->ref < REF_CONSTRUCT_START + REF_RANGE)
{
return COMP_CONSTRUCT;
}
if (Stat->ref >= REF_REPAIR_START &&
Stat->ref < REF_REPAIR_START + REF_RANGE)
{
return COMP_REPAIRUNIT;
}
return COMP_NUMCOMPONENTS;
}
bool StatGetComponentIMD(BASE_STATS *Stat, SDWORD compID, iIMDShape **CompIMD, iIMDShape **MountIMD)
{
WEAPON_STATS *psWStat;
*CompIMD = NULL;
*MountIMD = NULL;
switch (compID)
{
case COMP_BODY:
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_BRAIN:
psWStat = ((BRAIN_STATS *)Stat)->psWeaponStat;
*MountIMD = psWStat->pMountGraphic;
*CompIMD = psWStat->pIMD;
return true;
case COMP_WEAPON:
*MountIMD = ((WEAPON_STATS *)Stat)->pMountGraphic;
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_SENSOR:
*MountIMD = ((SENSOR_STATS *)Stat)->pMountGraphic;
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_ECM:
*MountIMD = ((ECM_STATS *)Stat)->pMountGraphic;
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_CONSTRUCT:
*MountIMD = ((CONSTRUCT_STATS *)Stat)->pMountGraphic;
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_PROPULSION:
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_REPAIRUNIT:
*MountIMD = ((REPAIR_STATS *)Stat)->pMountGraphic;
*CompIMD = ((COMPONENT_STATS *)Stat)->pIMD;
return true;
case COMP_NUMCOMPONENTS:
ASSERT(false, "Unknown component");
}
return false;
}
bool StatIsResearch(BASE_STATS *Stat)
{
return (Stat->ref >= REF_RESEARCH_START && Stat->ref <
REF_RESEARCH_START + REF_RANGE);
}
static void StatGetResearchImage(BASE_STATS *psStat, Image *image, iIMDShape **Shape, BASE_STATS **ppGraphicData, bool drawTechIcon)
{
if (drawTechIcon && ((RESEARCH *)psStat)->iconID != NO_RESEARCH_ICON)
{
*image = Image(IntImages, ((RESEARCH *)psStat)->iconID);
}
//if the research has a Stat associated with it - use this as display in the button
if (((RESEARCH *)psStat)->psStat)
{
*ppGraphicData = ((RESEARCH *)psStat)->psStat;
//make sure the IMDShape is initialised
*Shape = NULL;
}
else
{
//no stat so just just the IMD associated with the research
*Shape = ((RESEARCH *)psStat)->pIMD;
//make sure the stat is initialised
*ppGraphicData = NULL;
}
}
static void intDisplayBar(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, bool isPowerBar)
{
W_BARGRAPH *BarGraph = (W_BARGRAPH *)psWidget;
char szVal[30];
char const *szCheckWidth = "00000";
int x0 = xOffset + BarGraph->x();
int y0 = yOffset + BarGraph->y();
int arbitaryOffset = 3;
int iX, iY;
int barWidth = 100, width;
int i, precisionFactor = 1, value;
if (isPowerBar)
{
//draw the background image
iV_DrawImage(IntImages, IMAGE_DES_POWERBAR_LEFT, x0, y0);
iV_DrawImage(IntImages, IMAGE_DES_POWERBAR_RIGHT, x0 + psWidget->width() - iV_GetImageWidth(IntImages, IMAGE_DES_POWERBAR_RIGHT), y0);
}
// Arbitrary increment for the position of the bars
x0 += arbitaryOffset;
y0 += arbitaryOffset;
/* indent to allow text value */
iX = x0 + iV_GetTextWidth(szCheckWidth);
iY = y0 + (iV_GetImageHeight(IntImages, IMAGE_DES_STATSCURR) - iV_GetTextLineSize()) / 2 - iV_GetTextAboveBase();
if (isPowerBar)
{
// Adjust the width based on the text drawn
barWidth = BarGraph->width() - (iX - x0 + arbitaryOffset);
}
//draw current value section
width = MIN(BarGraph->majorSize * barWidth / 100, barWidth);
iV_DrawImageRepeatX(IntImages, IMAGE_DES_STATSCURR, iX, y0, width);
/* draw text value */
for (i = 0; i < BarGraph->precision; ++i)
{
precisionFactor *= 10;
}
value = (BarGraph->iOriginal * precisionFactor + BarGraph->denominator / 2) / BarGraph->denominator;
sprintf(szVal, "%d%s%.*d", value / precisionFactor, precisionFactor == 1 ? "" : ".", BarGraph->precision, value % precisionFactor);
iV_SetTextColour(WZCOL_TEXT_BRIGHT);
iV_DrawText(szVal, x0, iY);
//draw the comparison value - only if not zero
if (BarGraph->minorSize != 0)
{
width = MIN(BarGraph->minorSize * barWidth / 100, barWidth);
iV_DrawImage(IntImages, IMAGE_DES_STATSCOMP, iX + width, y0 - 1);
}
}
/* Draws a stats bar for the design screen */
void intDisplayStatsBar(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
intDisplayBar(psWidget, xOffset, yOffset, false);
}
/* Draws a Template Power Bar for the Design Screen */
void intDisplayDesignPowerBar(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
intDisplayBar(psWidget, xOffset, yOffset, true);
}
// Widget callback function to play an audio track.
//
#define WIDGETBEEPGAP (200) // 200 milliseconds between each beep please
void WidgetAudioCallback(int AudioID)
{
static SDWORD LastTimeAudio;
if (AudioID >= 0)
{
SDWORD TimeSinceLastWidgetBeep;
// Don't allow a widget beep if one was made in the last WIDGETBEEPGAP milliseconds
// This stops double beeps happening (which seems to happen all the time)
TimeSinceLastWidgetBeep = realTime - LastTimeAudio;
if (TimeSinceLastWidgetBeep < 0 || TimeSinceLastWidgetBeep > WIDGETBEEPGAP)
{
LastTimeAudio = realTime;
audio_PlayTrack(AudioID);
}
}
}
IntTransportButton::IntTransportButton(WIDGET *parent)
: IntFancyButton(parent)
, psDroid(nullptr)
{}
// Widget callback to display a contents button for the Transporter
void IntTransportButton::display(int xOffset, int yOffset)
{
// There should always be a droid associated with the button
ASSERT(psDroid != NULL, "Invalid droid pointer");
doRotation();
displayIMD(Image(), ImdObject::Droid(psDroid), xOffset, yOffset);
displayIfHighlight(xOffset, yOffset);
if (psDroid && missionForReInforcements())
{
// Add the experience level for each droid
unsigned gfxId = getDroidRankGraphic(psDroid);
if (gfxId != UDWORD_MAX)
{
/* Render the rank graphic at the correct location */
iV_DrawImage(IntImages, gfxId, xOffset + x() + 50, yOffset + y() + 30);
}
}
}
/* Draws blips on radar to represent Proximity Display and damaged structures */
void drawRadarBlips(int radarX, int radarY, float pixSizeH, float pixSizeV)
{
PROXIMITY_DISPLAY *psProxDisp;
UWORD imageID;
UDWORD delay = 150;
UDWORD i;
SDWORD width, height;
int x = 0, y = 0;
static const uint16_t imagesEnemy[] = {IMAGE_RAD_ENMREAD, IMAGE_RAD_ENM1, IMAGE_RAD_ENM2, IMAGE_RAD_ENM3};
static const uint16_t imagesResource[] = {IMAGE_RAD_RESREAD, IMAGE_RAD_RES1, IMAGE_RAD_RES2, IMAGE_RAD_RES3};
static const uint16_t imagesArtifact[] = {IMAGE_RAD_ARTREAD, IMAGE_RAD_ART1, IMAGE_RAD_ART2, IMAGE_RAD_ART3};
static const uint16_t imagesBurningResource[] = {IMAGE_RAD_BURNRESREAD, IMAGE_RAD_BURNRES1, IMAGE_RAD_BURNRES2, IMAGE_RAD_BURNRES3, IMAGE_RAD_BURNRES4, IMAGE_RAD_BURNRES5, IMAGE_RAD_BURNRES6};
static const uint16_t *const imagesProxTypes[] = {imagesEnemy, imagesResource, imagesArtifact};
// store the width & height of the radar/mini-map
width = scrollMaxX - scrollMinX;
height = scrollMaxY - scrollMinY;
// Check if it's time to remove beacons
for (i = 0; i < MAX_PLAYERS; i++)
{
/* Go through all the proximity Displays*/
for (psProxDisp = apsProxDisp[i]; psProxDisp != NULL; psProxDisp = psProxDisp->psNext)
{
if (psProxDisp->psMessage->dataType == MSG_DATA_BEACON)
{
MESSAGE *psCurrMsg = psProxDisp->psMessage;
VIEWDATA *pViewData = (VIEWDATA *)psCurrMsg->pViewData;
ASSERT(pViewData != NULL, "Message without data!");
if (pViewData->type == VIEW_BEACON)
{
ASSERT(pViewData->pData != NULL, "Help message without data!");
if (pViewData->pData != NULL && (((VIEW_PROXIMITY *)pViewData->pData)->timeAdded + 60000) <= gameTime)
{
debug(LOG_MSG, "blip timeout for %d, from %d", i, (((VIEW_PROXIMITY *)pViewData->pData)->sender));
removeMessage(psCurrMsg, i); //remove beacon
break; //there can only be 1 beacon per player
}
}
}
}
}
/* Go through all the proximity Displays */
for (psProxDisp = apsProxDisp[selectedPlayer]; psProxDisp != NULL; psProxDisp = psProxDisp->psNext)
{
unsigned animationLength = ARRAY_SIZE(imagesEnemy) - 1; // Same size as imagesResource and imagesArtifact.
const uint16_t *images;
if (psProxDisp->psMessage->player != selectedPlayer)
{
continue;
}
if (psProxDisp->type == POS_PROXDATA)
{
PROX_TYPE proxType = ((VIEW_PROXIMITY *)((VIEWDATA *)psProxDisp->psMessage->pViewData)->pData)->proxType;
images = imagesProxTypes[proxType];
}
else
{
FEATURE *psFeature = (FEATURE *)psProxDisp->psMessage->pViewData;
ASSERT(psFeature && psFeature->psStats, "Bad feature message");
if (psFeature && psFeature->psStats && psFeature->psStats->subType == FEAT_OIL_RESOURCE)
{
images = imagesResource;
if (fireOnLocation(psFeature->pos.x, psFeature->pos.y))
{
images = imagesBurningResource;
animationLength = ARRAY_SIZE(imagesBurningResource) - 1; // Longer animation for burning oil wells.
}
}
else
{
images = imagesArtifact;
}
}
// Draw the 'blips' on the radar - use same timings as radar blips if the message is read - don't animate
if (psProxDisp->psMessage->read)
{
imageID = images[0];
}
else
{
// Draw animated
if (realTime - psProxDisp->timeLastDrawn > delay)
{
++psProxDisp->strobe;
psProxDisp->timeLastDrawn = realTime;
}
psProxDisp->strobe %= animationLength;
imageID = images[1 + psProxDisp->strobe];
}
if (psProxDisp->type == POS_PROXDATA)
{
VIEW_PROXIMITY *psViewProx = (VIEW_PROXIMITY *)((VIEWDATA *)psProxDisp->psMessage->pViewData)->pData;
x = (psViewProx->x / TILE_UNITS - scrollMinX) * pixSizeH;
y = (psViewProx->y / TILE_UNITS - scrollMinY) * pixSizeV;
}
else if (psProxDisp->type == POS_PROXOBJ)
{
x = (((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.x / TILE_UNITS - scrollMinX) * pixSizeH;
y = (((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.y / TILE_UNITS - scrollMinY) * pixSizeV;
}
else
{
ASSERT(false, "Bad message type");
continue;
}
// NOTE: On certain missions (limbo & expand), there is still valid data that is stored outside the
// normal radar/mini-map view. We must now calculate the radar/mini-map's bounding box, and clip
// everything outside the box.
if ((x + radarX) < width * pixSizeV / 2 && (x + radarX) > -width * pixSizeV / 2
&& (y + radarY) < height * pixSizeH / 2 && (y + radarY) > -height * pixSizeH / 2)
{
// Draw the 'blip'
iV_DrawImage(IntImages, imageID, x + radarX, y + radarY);
}
}
if (audio_GetPreviousQueueTrackRadarBlipPos(&x, &y))
{
unsigned animationLength = ARRAY_SIZE(imagesEnemy) - 1;
int strobe = (realTime / delay) % animationLength;
x = (x / TILE_UNITS - scrollMinX) * pixSizeH;
y = (y / TILE_UNITS - scrollMinY) * pixSizeV;
imageID = imagesEnemy[strobe];
// NOTE: On certain missions (limbo & expand), there is still valid data that is stored outside the
// normal radar/mini-map view. We must now calculate the radar/mini-map's bounding box, and clip
// everything outside the box.
if ((x + radarX) < width * pixSizeV / 2 && (x + radarX) > -width * pixSizeV / 2
&& (y + radarY) < height * pixSizeH / 2 && (y + radarY) > -height * pixSizeH / 2)
{
// Draw the 'blip'
iV_DrawImage(IntImages, imageID, x + radarX, y + radarY);
}
}
}
/*Displays the proximity messages blips over the world*/
void intDisplayProximityBlips(WIDGET *psWidget, WZ_DECL_UNUSED UDWORD xOffset, WZ_DECL_UNUSED UDWORD yOffset)
{
W_CLICKFORM *psButton = (W_CLICKFORM *)psWidget;
PROXIMITY_DISPLAY *psProxDisp = (PROXIMITY_DISPLAY *)psButton->pUserData;
MESSAGE *psMsg = psProxDisp->psMessage;
SDWORD x = 0, y = 0;
ASSERT(psMsg->type == MSG_PROXIMITY, "Invalid message type");
//if no data - ignore message
if (psMsg->pViewData == NULL || psMsg->player != selectedPlayer)
{
return;
}
if (psProxDisp->type == POS_PROXDATA)
{
x = ((VIEW_PROXIMITY *)((VIEWDATA *)psProxDisp->psMessage->pViewData)->pData)->x;
y = ((VIEW_PROXIMITY *)((VIEWDATA *)psProxDisp->psMessage->pViewData)->pData)->y;
}
else if (psProxDisp->type == POS_PROXOBJ)
{
x = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.x;
y = ((BASE_OBJECT *)psProxDisp->psMessage->pViewData)->pos.y;
}
//if not within view ignore message
if (!clipXY(x, y))
{
return;
}
//if the message is read - don't draw
if (!psMsg->read)
{
//set the button's x/y so that can be clicked on
psButton->move(psProxDisp->screenX - psButton->width() / 2, psProxDisp->screenY - psButton->height() / 2);
}
}
static UDWORD sliderMousePos(W_SLIDER *Slider)
{
return (Slider->parent()->x() + Slider->x())
+ ((Slider->pos * Slider->width()) / Slider->numStops);
}
static UWORD sliderMouseUnit(W_SLIDER *Slider)
{
UWORD posStops = (UWORD)(Slider->numStops / 20);
if (posStops == 0 || Slider->pos == 0 || Slider->pos == Slider->numStops)
{
return 1;
}
if (Slider->pos < posStops)
{
return (Slider->pos);
}
if (Slider->pos > (Slider->numStops - posStops))
{
return (UWORD)(Slider->numStops - Slider->pos);
}
return posStops;
}
void intUpdateQuantitySlider(WIDGET *psWidget, W_CONTEXT *psContext)
{
W_SLIDER *Slider = (W_SLIDER *)psWidget;
if (Slider->state & SLD_HILITE)
{
if (keyDown(KEY_LEFTARROW))
{
if (Slider->pos > 0)
{
Slider->pos = (UWORD)(Slider->pos - sliderMouseUnit(Slider));
setMousePos(sliderMousePos(Slider), mouseY()); // move mouse
}
}
else if (keyDown(KEY_RIGHTARROW))
{
if (Slider->pos < Slider->numStops)
{
Slider->pos = (UWORD)(Slider->pos + sliderMouseUnit(Slider));
setMousePos(sliderMousePos(Slider), mouseY()); // move mouse
}
}
}
}
void intDisplayResSubGroup(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_LABEL *Label = (W_LABEL *)psWidget;
UDWORD x = Label->x() + xOffset;
UDWORD y = Label->y() + yOffset;
RESEARCH *psResearch = (RESEARCH *)Label->pUserData;
if (psResearch->subGroup != NO_RESEARCH_ICON)
{
iV_DrawImage(IntImages, psResearch->subGroup, x, y);
}
}
void intDisplayAllyIcon(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_LABEL *Label = (W_LABEL *)psWidget;
UDWORD x = Label->x() + xOffset;
UDWORD y = Label->y() + yOffset;
unsigned ref = UNPACKDWORD_HI(psWidget->UserData) + REF_RESEARCH_START;
unsigned num = UNPACKDWORD_LOW(psWidget->UserData);
std::vector<AllyResearch> const &researches = listAllyResearch(ref);
if (num >= researches.size())
{
return; // No icon to display. (Shouldn't really get here...)
}
if (!researches[num].active && realTime % 500 >= 250)
{
return; // If inactive, blink the icon. (Alternatively, we could use a different icon instead.)
}
iV_DrawImageTc(IntImages, IMAGE_ALLY_RESEARCH, IMAGE_ALLY_RESEARCH_TC, x, y, pal_GetTeamColour(getPlayerColour(researches[num].player)));
}
void intDisplayAllyBar(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
{
W_BARGRAPH *psBar = (W_BARGRAPH *)psWidget;
RESEARCH const &research = asResearch[psWidget->UserData];
std::vector<AllyResearch> const &researches = listAllyResearch(research.ref);
unsigned bestCompletion = 0;
const int researchNotStarted = 3600000;
int bestPowerNeeded = researchNotStarted;
int bestTimeToResearch = researchNotStarted;
int researchPowerCost = researchNotStarted;
for (std::vector<AllyResearch>::const_iterator i = researches.begin(); i != researches.end(); ++i)
{
if (bestCompletion < i->completion)
{
bestCompletion = i->completion;
psBar->majorCol = pal_GetTeamColour(getPlayerColour(i->player));
}
if (!i->active)
{
continue; // Don't show remaining time/power, if the facility is currently being upgraded.
}
if (i->powerNeeded == -1)
{
bestTimeToResearch = std::min<unsigned>(bestTimeToResearch, i->timeToResearch);
}
else
{
bestPowerNeeded = std::min<unsigned>(bestPowerNeeded, i->powerNeeded);
researchPowerCost = research.researchPower;
}
}
setBarGraphValue(psBar, psBar->majorCol, bestCompletion, research.researchPoints);
if (bestTimeToResearch != researchNotStarted)
{
// Show research progress.
formatTimeText(psBar, bestTimeToResearch);
}
else if (bestCompletion > 0)
{
// Waiting for module...
psBar->text = QString::fromUtf8("—*—");
}
else if (bestPowerNeeded != researchNotStarted)
{
// Show how much power is needed, before research can start.
formatPowerText(psBar, bestPowerNeeded);
setBarGraphValue(psBar, psBar->majorCol, researchPowerCost - bestPowerNeeded, researchPowerCost);
}
barGraphDisplayTrough(psWidget, xOffset, yOffset);
}
/* Set the shadow power for the selected player */
void intSetShadowPower(int quantity)
{
ManuPower = quantity;
}