Skip to content

Commit

Permalink
Base defense facilities with own ammo storage and rearm rate
Browse files Browse the repository at this point in the history
https://openxcom.org/forum/index.php/topic,11760.0.html

Also fixed old issue where ammo was not spent when base defenses missed:
https://openxcom.org/forum/index.php/topic,7651.0.html
  • Loading branch information
MeridianOXC committed Jan 11, 2024
1 parent cf951c3 commit 1ade2a5
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 11 deletions.
1 change: 1 addition & 0 deletions bin/common/Language/OXCE/en-GB.yml
Expand Up @@ -440,6 +440,7 @@ en-GB:
STR_AUTO_PATROL: "AUTO-PATROL"
STR_ETA: "ETA>{ALT}{0}"
#GeoscapeState.cpp
STR_NOT_ENOUGH_ITEM_TO_REARM_FACILITY_AT_BASE: "Not enough {0} to rearm {1} at {2}"
STR_UFO_STARTED_HUNTING: "{0} is on an intercept course with {1}"
STR_CRAFT_IS_READY: "{0} at {1} is ready."
STR_ECONOMY_WARNING: "The funds {0} and income {1} are low and the maintenance {2} too high! Sell something to avoid taking a credit... funds missing: {3}"
Expand Down
1 change: 1 addition & 0 deletions bin/common/Language/OXCE/en-US.yml
Expand Up @@ -440,6 +440,7 @@ en-US:
STR_AUTO_PATROL: "AUTO-PATROL"
STR_ETA: "ETA>{ALT}{0}"
#GeoscapeState.cpp
STR_NOT_ENOUGH_ITEM_TO_REARM_FACILITY_AT_BASE: "Not enough {0} to rearm {1} at {2}"
STR_UFO_STARTED_HUNTING: "{0} is on an intercept course with {1}"
STR_CRAFT_IS_READY: "{0} at {1} is ready."
STR_ECONOMY_WARNING: "The funds {0} and income {1} are low and the maintenance {2} too high! Sell something to avoid taking a credit... funds missing: {3}"
Expand Down
5 changes: 5 additions & 0 deletions bin/standard/xcom1/interfaces.rul
Expand Up @@ -1875,6 +1875,11 @@ interfaces:
- id: baseView
color: 213 # gold (time remaining)
color2: 16 # orange (selector color)
- id: trafficLights
color: 32 # red
color2: 144 # yellow
border: 48 # green
TFTDMode: false
- id: errorPalette
color: 6 # oxide
- id: errorMessage
Expand Down
5 changes: 5 additions & 0 deletions bin/standard/xcom2/interfaces.rul
Expand Up @@ -1894,6 +1894,11 @@ interfaces: #done
- id: baseView
color: 213 # gold (time remaining)
color2: 16 # orange (selector color)
- id: trafficLights
color: 8 # red
color2: 163 # yellow
border: 81 # green
TFTDMode: true
- id: errorPalette
color: 6 # oxide
- id: errorMessage
Expand Down
36 changes: 35 additions & 1 deletion src/Basescape/BaseView.cpp
Expand Up @@ -41,7 +41,12 @@ namespace OpenXcom
* @param x X position in pixels.
* @param y Y position in pixels.
*/
BaseView::BaseView(int width, int height, int x, int y) : InteractiveSurface(width, height, x, y), _base(0), _texture(0), _selFacility(0), _big(0), _small(0), _lang(0), _gridX(0), _gridY(0), _selSizeX(0), _selSizeY(0), _selector(0), _blink(true), _cellColor(0), _selectorColor(0)
BaseView::BaseView(int width, int height, int x, int y) : InteractiveSurface(width, height, x, y),
_base(0), _texture(0), _selFacility(0), _big(0), _small(0), _lang(0),
_gridX(0), _gridY(0), _selSizeX(0), _selSizeY(0),
_selector(0), _blink(true),
_redColor(0), _yellowColor(0), _greenColor(0), _highContrast(true),
_cellColor(0), _selectorColor(0)
{
// Clear grid
for (int i = 0; i < BASE_SIZE; ++i)
Expand Down Expand Up @@ -605,6 +610,28 @@ void BaseView::draw()
text->blit(this->getSurface());
delete text;
}

// Draw ammo indicator
if (fac->getBuildTime() == 0 && fac->getRules()->getAmmoMax() > 0)
{
Text* text = new Text(GRID_SIZE * fac->getRules()->getSizeX(), 9, 0, 0);
text->setPalette(getPalette());
text->initText(_big, _small, _lang);
text->setX(fac->getX() * GRID_SIZE);
text->setY(fac->getY() * GRID_SIZE);
text->setHighContrast(_highContrast);
if (fac->getAmmo() >= fac->getRules()->getAmmoMax())
text->setColor(_greenColor); // 100%
else if (fac->getAmmo() <= fac->getRules()->getAmmoMax() / 2)
text->setColor(_redColor); // 0-50%
else
text->setColor(_yellowColor); // 51-99%
std::ostringstream ss;
ss << fac->getAmmo() << "/" << fac->getRules()->getAmmoMax();
text->setText(ss.str());
text->blit(this->getSurface());
delete text;
}
}
}

Expand Down Expand Up @@ -683,5 +710,12 @@ void BaseView::setSecondaryColor(Uint8 color)
{
_selectorColor = color;
}
void BaseView::setOtherColors(Uint8 red, Uint8 yellow, Uint8 green, bool highContrast)
{
_redColor = red;
_yellowColor = yellow;
_greenColor = green;
_highContrast = highContrast;
}

}
4 changes: 3 additions & 1 deletion src/Basescape/BaseView.h
Expand Up @@ -52,6 +52,8 @@ class BaseView : public InteractiveSurface
Surface *_selector;
bool _blink;
Timer *_timer;
Uint8 _redColor, _yellowColor, _greenColor;
bool _highContrast;
Uint8 _cellColor, _selectorColor;
/// Updates the neighborFacility's build time. This is for internal use only (reCalcQueuedBuildings()).
void updateNeighborFacilityBuildTime(BaseFacility* facility, BaseFacility* neighbor);
Expand Down Expand Up @@ -96,8 +98,8 @@ class BaseView : public InteractiveSurface
void mouseOut(Action *action, State *state) override;

void setColor(Uint8 color) override;

void setSecondaryColor(Uint8 color) override;
void setOtherColors(Uint8 red, Uint8 yellow, Uint8 green, bool highContrast);
};

}
5 changes: 5 additions & 0 deletions src/Basescape/BasescapeState.cpp
Expand Up @@ -113,6 +113,11 @@ BasescapeState::BasescapeState(Base *base, Globe *globe) : _base(base), _globe(g
centerAllSurfaces();

// Set up objects
auto* itf = _game->getMod()->getInterface("basescape")->getElement("trafficLights");
if (itf)
{
_view->setOtherColors(itf->color, itf->color2, itf->border, !itf->TFTDMode);
}
_view->setTexture(_game->getMod()->getSurfaceSet("BASEBITS.PCK"));
_view->onMouseClick((ActionHandler)&BasescapeState::viewLeftClick, SDL_BUTTON_LEFT);
_view->onMouseClick((ActionHandler)&BasescapeState::viewRightClick, SDL_BUTTON_RIGHT);
Expand Down
5 changes: 5 additions & 0 deletions src/Basescape/DismantleFacilityState.cpp
Expand Up @@ -137,6 +137,11 @@ void DismantleFacilityState::btnOkClick(Action *)
_base->getStorageItems()->addItem(pair.first, pair.second.second);
}
}
if (_fac->getRules()->getAmmoMax() > 0 && _fac->getRules()->getAmmoItem() && _fac->getAmmo() > 0)
{
// Full refund of loaded ammo
_base->getStorageItems()->addItem(_fac->getRules()->getAmmoItem(), _fac->getAmmo());
}

for (auto facIt = _base->getFacilities()->begin(); facIt != _base->getFacilities()->end(); ++facIt)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Basescape/PlaceFacilityState.cpp
Expand Up @@ -85,6 +85,11 @@ PlaceFacilityState::PlaceFacilityState(Base *base, const RuleBaseFacility *rule,
// Set up objects
setWindowBackground(_window, "placeFacility");

auto* itf = _game->getMod()->getInterface("basescape")->getElement("trafficLights");
if (itf)
{
_view->setOtherColors(itf->color, itf->color2, itf->border, !itf->TFTDMode);
}
_view->setTexture(_game->getMod()->getSurfaceSet("BASEBITS.PCK"));
_view->setBase(_base);
_view->setSelectable(rule->getSizeX(), rule->getSizeY());
Expand Down
6 changes: 6 additions & 0 deletions src/Basescape/PlaceLiftState.cpp
Expand Up @@ -33,6 +33,7 @@
#include "SelectStartFacilityState.h"
#include "../Savegame/SavedGame.h"
#include "../Ufopaedia/Ufopaedia.h"
#include "../Mod/RuleInterface.h"

namespace OpenXcom
{
Expand Down Expand Up @@ -67,6 +68,11 @@ PlaceLiftState::PlaceLiftState(Base *base, Globe *globe, bool first) : _base(bas
// Set up objects
setWindowBackground(_window, "selectFacility");

auto* itf = _game->getMod()->getInterface("basescape")->getElement("trafficLights");
if (itf)
{
_view->setOtherColors(itf->color, itf->color2, itf->border, !itf->TFTDMode);
}
_view->setTexture(_game->getMod()->getSurfaceSet("BASEBITS.PCK"));
_view->setBase(_base);

Expand Down
10 changes: 10 additions & 0 deletions src/Basescape/StoresState.cpp
Expand Up @@ -30,6 +30,7 @@
#include "../Interface/ToggleTextButton.h"
#include "../Interface/Window.h"
#include "../Savegame/Base.h"
#include "../Savegame/BaseFacility.h"
#include "../Savegame/Craft.h"
#include "../Savegame/ItemContainer.h"
#include "../Savegame/ResearchProject.h"
Expand Down Expand Up @@ -256,6 +257,15 @@ void StoresState::initList()
// 1. items in base stores
qty += xbase->getStorageItems()->getItem(rule);

// 1b. items from base defense facilities
for (const auto* facility : *xbase->getFacilities())
{
if (facility->getRules()->getAmmoMax() > 0 && facility->getRules()->getAmmoItem() == rule)
{
qty += facility->getAmmo();
}
}

// 2. items from craft
for (const auto* craft : *xbase->getCrafts())
{
Expand Down
32 changes: 26 additions & 6 deletions src/Geoscape/BaseDefenseState.cpp
Expand Up @@ -193,6 +193,8 @@ void BaseDefenseState::nextStep()
BaseFacility* def = _base->getDefenses()->at(_attacks);
const RuleItem* ammo = (def)->getRules()->getAmmoItem();
int ammoNeeded = (def)->getRules()->getAmmoNeeded();
bool hasOwnAmmo = def->getRules()->getAmmoMax() > 0;
bool spendAmmo = false;

switch (_action)
{
Expand All @@ -206,7 +208,11 @@ void BaseDefenseState::nextStep()
}
return;
case BDA_FIRE:
if (ammo && _base->getStorageItems()->getItem(ammo) < ammoNeeded)
if (hasOwnAmmo && def->getAmmo() < ammoNeeded)
{
_lstDefenses->setCellText(_row, 1, tr("STR_NO_AMMO"));
}
else if (!hasOwnAmmo && ammo && _base->getStorageItems()->getItem(ammo) < ammoNeeded)
{
_lstDefenses->setCellText(_row, 1, tr("STR_NO_AMMO"));
}
Expand All @@ -219,20 +225,22 @@ void BaseDefenseState::nextStep()
_action = BDA_RESOLVE;
return;
case BDA_RESOLVE:
if (ammo && _base->getStorageItems()->getItem(ammo) < ammoNeeded)
if (hasOwnAmmo && def->getAmmo() < ammoNeeded)
{
//_lstDefenses->setCellText(_row, 2, tr("STR_NO_AMMO"));
}
else if (!hasOwnAmmo && ammo && _base->getStorageItems()->getItem(ammo) < ammoNeeded)
{
//_lstDefenses->setCellText(_row, 2, tr("STR_NO_AMMO"));
}
else if (!RNG::percent((def)->getRules()->getHitRatio()))
{
spendAmmo = true;
_lstDefenses->setCellText(_row, 2, tr("STR_MISSED"));
}
else
{
if (ammo && ammoNeeded > 0)
{
_base->getStorageItems()->removeItem(ammo, ammoNeeded);
}
spendAmmo = true;
_lstDefenses->setCellText(_row, 2, tr("STR_HIT"));
_game->getMod()->getSound("GEO.CAT", (def)->getRules()->getHitSound())->play();
int dmg = (def)->getRules()->getDefenseValue();
Expand All @@ -245,6 +253,18 @@ void BaseDefenseState::nextStep()
}
_ufo->setDamage(_ufo->getDamage() + dmg, _game->getMod());
}
if (spendAmmo && ammoNeeded > 0)
{
if (hasOwnAmmo)
{
def->setAmmo(def->getAmmo() - ammoNeeded);
def->resetAmmoMissingReported();
}
else if (!hasOwnAmmo && ammo)
{
_base->getStorageItems()->removeItem(ammo, ammoNeeded);
}
}
if (_ufo->getStatus() == Ufo::DESTROYED)
_action = BDA_DESTROY;
else
Expand Down
17 changes: 17 additions & 0 deletions src/Geoscape/GeoscapeState.cpp
Expand Up @@ -2105,6 +2105,23 @@ void GeoscapeState::time1Hour()
}
}

// Handle base defenses maintenance
for (auto* xbase : *_game->getSavedGame()->getBases())
{
for (auto* facility : *xbase->getFacilities())
{
auto* ammo = facility->rearm();
if (ammo)
{
std::string msg = tr("STR_NOT_ENOUGH_ITEM_TO_REARM_FACILITY_AT_BASE")
.arg(tr(ammo->getType()))
.arg(tr(facility->getRules()->getType()))
.arg(xbase->getName());
popup(new CraftErrorState(this, msg));
}
}
}

// Handle transfers
bool window = false;
for (auto* xbase : *_game->getSavedGame()->getBases())
Expand Down
4 changes: 3 additions & 1 deletion src/Mod/RuleBaseFacility.cpp
Expand Up @@ -43,7 +43,7 @@ RuleBaseFacility::RuleBaseFacility(const std::string &type, int listOrder) :
_storage(0), _personnel(0), _aliens(0), _crafts(0), _labs(0), _workshops(0), _psiLabs(0),
_spriteEnabled(false),
_sightRange(0), _sightChance(0), _radarRange(0), _radarChance(0),
_defense(0), _hitRatio(0), _fireSound(0), _hitSound(0), _placeSound(-1), _ammoNeeded(1), _listOrder(listOrder),
_defense(0), _hitRatio(0), _fireSound(0), _hitSound(0), _placeSound(-1), _ammoMax(0), _rearmRate(1), _ammoNeeded(1), _listOrder(listOrder),
_trainingRooms(0), _maxAllowedPerBase(0), _sickBayAbsoluteBonus(0.0f), _sickBayRelativeBonus(0.0f),
_prisonType(0), _rightClickActionType(0), _verticalLevels(), _removalTime(0), _canBeBuiltOver(false), _destroyedFacility(0)
{
Expand Down Expand Up @@ -119,6 +119,8 @@ void RuleBaseFacility::load(const YAML::Node &node, Mod *mod)
mod->loadSoundOffset(_type, _hitSound, node["hitSound"], "GEO.CAT");
mod->loadSoundOffset(_type, _placeSound, node["placeSound"], "GEO.CAT");

_ammoMax = node["ammoMax"].as<int>(_ammoMax);
_rearmRate = node["rearmRate"].as<int>(_rearmRate);
_ammoNeeded = node["ammoNeeded"].as<int>(_ammoNeeded);
_ammoItemName = node["ammoItem"].as<std::string>(_ammoItemName);
_mapName = node["mapName"].as<std::string>(_mapName);
Expand Down
5 changes: 5 additions & 0 deletions src/Mod/RuleBaseFacility.h
Expand Up @@ -61,6 +61,7 @@ class RuleBaseFacility
bool _spriteEnabled;
int _sightRange, _sightChance;
int _radarRange, _radarChance, _defense, _hitRatio, _fireSound, _hitSound, _placeSound;
int _ammoMax, _rearmRate;
int _ammoNeeded;
const RuleItem* _ammoItem;
std::string _ammoItemName;
Expand Down Expand Up @@ -171,6 +172,10 @@ class RuleBaseFacility
/// Gets the facility's weapon hit ratio.
int getHitRatio() const;
/// Gets the facility's weapon ammo capacity.
int getAmmoMax() const { return _ammoMax; }
/// Gets the facility's weapon rearm rate.
int getRearmRate() const { return _rearmRate; }
/// Gets the facility's weapon ammo spent per shot.
int getAmmoNeeded() const { return _ammoNeeded; }
/// Gets the facility's weapon ammo item.
const RuleItem* getAmmoItem() const { return _ammoItem; }
Expand Down

0 comments on commit 1ade2a5

Please sign in to comment.