From f44ef8967fcaf00b2c21ccdb71ab1efe8607fc86 Mon Sep 17 00:00:00 2001 From: Austin Stanley Date: Wed, 12 Apr 2023 11:29:58 -0500 Subject: [PATCH 1/3] Implementation of scriptable sprite overlays. --- src/Battlescape/AlienInventory.cpp | 47 ++-- src/Battlescape/AlienInventory.h | 2 +- src/Battlescape/BattlescapeState.cpp | 121 ++-------- src/Battlescape/BattlescapeState.h | 5 +- src/Battlescape/Inventory.cpp | 183 +++++---------- src/Battlescape/Inventory.h | 10 +- src/Battlescape/InventoryItemSprite.cpp | 289 ++++++++++++++++++++++++ src/Battlescape/InventoryItemSprite.h | 159 +++++++++++++ src/Battlescape/InventoryState.cpp | 18 +- src/Battlescape/SpriteOverlay.cpp | 211 +++++++++++++++++ src/Battlescape/SpriteOverlay.h | 117 ++++++++++ src/Engine/Surface.h | 1 - src/Mod/Mod.cpp | 56 +++++ src/Mod/ModScript.h | 26 +++ src/Mod/RuleInventory.h | 4 + src/Mod/RuleItem.cpp | 44 ++-- src/Mod/RuleItem.h | 2 +- src/OpenXcom.2010.vcxproj | 6 +- src/Savegame/BattleItem.cpp | 63 ++++++ src/Savegame/BattleItem.h | 4 + src/Savegame/BattleUnit.cpp | 21 +- src/Savegame/SavedBattleGame.cpp | 16 +- 22 files changed, 1121 insertions(+), 284 deletions(-) create mode 100644 src/Battlescape/InventoryItemSprite.cpp create mode 100644 src/Battlescape/InventoryItemSprite.h create mode 100644 src/Battlescape/SpriteOverlay.cpp create mode 100644 src/Battlescape/SpriteOverlay.h diff --git a/src/Battlescape/AlienInventory.cpp b/src/Battlescape/AlienInventory.cpp index f9ee038548..1ebde6f0a0 100644 --- a/src/Battlescape/AlienInventory.cpp +++ b/src/Battlescape/AlienInventory.cpp @@ -31,7 +31,9 @@ #include "../Mod/RuleInterface.h" #include "../Savegame/BattleUnit.h" #include "../Savegame/SavedGame.h" +#include "../Savegame/SavedBattleGame.h" #include "../Ufopaedia/Ufopaedia.h" +#include "InventoryItemSprite.h" namespace OpenXcom { @@ -151,40 +153,39 @@ void AlienInventory::drawGrid() /** * Draws the items contained in the alien's hands. */ -void AlienInventory::drawItems() +void AlienInventory::drawItems() const { const SavedBattleGame* save = _game->getSavedGame()->getSavedBattle(); ScriptWorkerBlit work; _items->clear(); - if (_selUnit != 0) + if (_selUnit != nullptr) { - SurfaceSet *texture = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); + const SurfaceSet* surfaceSet = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); for (const auto* item : *_selUnit->getInventory()) { - if (item->getSlot()->getType() == INV_HAND) - { - const Surface* frame = item->getBigSprite(texture, save, _animFrame); - - if (!frame) - continue; + if (item->getSlot()->getType() != INV_HAND) { continue; } - int x = item->getSlot()->getX() + item->getRules()->getHandSpriteOffX(); - x += _game->getMod()->getAlienInventoryOffsetX(); + const auto handSlot = item->getSlot(); + SDL_Rect spriteBounds = item->getInvSpriteBounds(); + spriteBounds.x += handSlot->getX() + _game->getMod()->getAlienInventoryOffsetX(); + spriteBounds.y += handSlot->getY(); - if (item->getSlot()->isRightHand()) - x -= _dynamicOffset; - else if (item->getSlot()->isLeftHand()) - x += _dynamicOffset; + // offset bounds by dynamic offset to account for large aliens. + spriteBounds.x -= (handSlot->isRightHand() ? -_dynamicOffset : + handSlot->isLeftHand() ? _dynamicOffset : + throw std::logic_error("Item in hand slot with bad hand value.")); - int y = item->getSlot()->getY() + item->getRules()->getHandSpriteOffY(); + InventoryItemSprite(*item, save, *_items, spriteBounds).draw(*surfaceSet, InventorySpriteContext::ALIEN_INV_HAND, _animFrame); - BattleItem::ScriptFill(&work, item, save, BODYPART_ITEM_INVENTORY, _animFrame, 0); - work.executeBlit(frame, _items, x, y, 0); - } - else - { - continue; - } + /// offset for hand overlay + auto handSlotBounds = SDL_Rect{ + static_cast(handSlot->getX() + 1 + _game->getMod()->getAlienInventoryOffsetX()), + static_cast(handSlot->getY() + 1), + RuleInventory::HAND_SLOT_W-1, + RuleInventory::HAND_SLOT_H-2, + }; + // this should render no default effects, but allows for scripting. + InventoryItemSprite(*item, save, *_items, handSlotBounds).drawHandOverlay(InventorySpriteContext::ALIEN_INV_HAND, _animFrame); } } } diff --git a/src/Battlescape/AlienInventory.h b/src/Battlescape/AlienInventory.h index 9e41ac155e..cd71c19766 100644 --- a/src/Battlescape/AlienInventory.h +++ b/src/Battlescape/AlienInventory.h @@ -58,7 +58,7 @@ class AlienInventory : public InteractiveSurface /// Draws the inventory grid. void drawGrid(); /// Draws the inventory items. - void drawItems(); + void drawItems() const; /// Blits the inventory onto another surface. void blit(SDL_Surface *surface) override; /// Special handling for mouse clicks. diff --git a/src/Battlescape/BattlescapeState.cpp b/src/Battlescape/BattlescapeState.cpp index b6b3d11612..3fe36e6ad9 100644 --- a/src/Battlescape/BattlescapeState.cpp +++ b/src/Battlescape/BattlescapeState.cpp @@ -31,6 +31,8 @@ #include "UnitInfoState.h" #include "InventoryState.h" #include "AlienInventoryState.h" +#include "InventoryItemSprite.h" +#include "SpriteOverlay.h" #include "Pathfinding.h" #include "BattlescapeGame.h" #include "WarningMessage.h" @@ -163,22 +165,6 @@ BattlescapeState::BattlescapeState() : _btnZeroTUs = new BattlescapeButton(10, 23, x + 49, y + 33); _btnLeftHandItem = new InteractiveSurface(32, 48, x + 8, y + 4); _btnRightHandItem = new InteractiveSurface(32, 48, x + 280, y + 4); - _numAmmoLeft.reserve(RuleItem::AmmoSlotMax); - _numAmmoRight.reserve(RuleItem::AmmoSlotMax); - for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) - { - _numAmmoLeft.push_back(new NumberText(30, 5, x + 8, y + 4 + 6 * slot)); - _numAmmoRight.push_back(new NumberText(30, 5, x + 280, y + 4 + 6 * slot)); - } - _numMedikitLeft.reserve(RuleItem::MedikitSlots); - _numMedikitRight.reserve(RuleItem::MedikitSlots); - for (int slot = 0; slot < RuleItem::MedikitSlots; ++slot) - { - _numMedikitLeft.push_back(new NumberText(30, 5, x + 9, y + 32 + 7 * slot)); - _numMedikitRight.push_back(new NumberText(30, 5, x + 281, y + 32 + 7 * slot)); - } - _numTwoHandedIndicatorLeft = new NumberText(10, 5, x + 36, y + 46); - _numTwoHandedIndicatorRight = new NumberText(10, 5, x + 308, y + 46); const int visibleUnitX = _game->getMod()->getInterface("battlescape")->getElement("visibleUnits")->x; const int visibleUnitY = _game->getMod()->getInterface("battlescape")->getElement("visibleUnits")->y; for (int i = 0; i < VISIBLE_MAX; ++i) @@ -357,18 +343,6 @@ BattlescapeState::BattlescapeState() : add(_btnZeroTUs, "buttonZeroTUs", "battlescape", _icons); add(_btnLeftHandItem, "buttonLeftHand", "battlescape", _icons); add(_btnRightHandItem, "buttonRightHand", "battlescape", _icons); - for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) - { - add(_numAmmoLeft[slot], "numAmmoLeft", "battlescape", _icons); - add(_numAmmoRight[slot], "numAmmoRight", "battlescape", _icons); - } - for (int slot = 0; slot < RuleItem::MedikitSlots; ++slot) - { - add(_numMedikitLeft[slot], "numMedikitLeft", "battlescape", _icons); - add(_numMedikitRight[slot], "numMedikitRight", "battlescape", _icons); - } - add(_numTwoHandedIndicatorLeft, "numTwoHandedIndicatorLeft", "battlescape", _icons); - add(_numTwoHandedIndicatorRight, "numTwoHandedIndicatorRight", "battlescape", _icons); for (int i = 0; i < VISIBLE_MAX; ++i) { add(_btnVisibleUnit[i]); @@ -415,19 +389,6 @@ BattlescapeState::BattlescapeState() : _numLayers->setColor(Palette::blockOffset(1)-2); _numLayers->setValue(1); - for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) - { - _numAmmoLeft[slot]->setValue(999); - _numAmmoRight[slot]->setValue(999); - } - for (int slot = 0; slot < RuleItem::MedikitSlots; ++slot) - { - _numMedikitLeft[slot]->setValue(999); - _numMedikitRight[slot]->setValue(999); - } - _numTwoHandedIndicatorLeft->setValue(2); - _numTwoHandedIndicatorRight->setValue(2); - _icons->onMouseIn((ActionHandler)&BattlescapeState::mouseInIcons); _icons->onMouseOut((ActionHandler)&BattlescapeState::mouseOutIcons); @@ -1875,72 +1836,19 @@ bool BattlescapeState::playableUnitSelected() } /** - * Draw hand item with ammo number. + * Draw hand item inventory sprite and reaction indicator. */ -void BattlescapeState::drawItem(BattleItem* item, Surface* hand, std::vector &ammoText, std::vector &medikitText, NumberText *twoHandedText, bool drawReactionIndicator) +void BattlescapeState::drawItem(const BattleItem* item, Surface* hand, bool drawReactionIndicator) const { hand->clear(); - for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) - { - ammoText[slot]->setVisible(false); - } - for (int slot = 0; slot < RuleItem::MedikitSlots; ++slot) - { - medikitText[slot]->setVisible(false); - } - twoHandedText->setVisible(false); + if (item) { - const RuleItem *rule = item->getRules(); - rule->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), hand, item, _save, _save->getAnimFrame()); - for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) - { - if (item->isAmmoVisibleForSlot(slot)) - { - BattleItem* ammo = item->getAmmoForSlot(slot); - if (!ammo) - { - ammoText[slot]->setVisible(true); - ammoText[slot]->setValue(0); - } - else - { - ammoText[slot]->setVisible(true); - ammoText[slot]->setValue(ammo->getAmmoQuantity()); - } - } - } - twoHandedText->setVisible(rule->isTwoHanded()); - twoHandedText->setColor(rule->isBlockingBothHands() ? _twoHandedRed : _twoHandedGreen); - if (rule->getBattleType() == BT_MEDIKIT) - { - medikitText[0]->setVisible(true); - medikitText[0]->setValue(item->getPainKillerQuantity()); - medikitText[1]->setVisible(true); - medikitText[1]->setValue(item->getStimulantQuantity()); - medikitText[2]->setVisible(true); - medikitText[2]->setValue(item->getHealQuantity()); - } + const SurfaceSet* surfaceSet = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); + int animFrame = _save->getAnimFrame(); - // primed grenade indicator (static) - /* - if (item->getFuseTimer() >= 0) - { - Surface *tempSurface = _game->getMod()->getSurfaceSet("SCANG.DAT")->getFrame(6); - tempSurface->setX((RuleInventory::HAND_W - rule->getInventoryWidth()) * RuleInventory::SLOT_W / 2); - tempSurface->setY((RuleInventory::HAND_H - rule->getInventoryHeight()) * RuleInventory::SLOT_H / 2); - tempSurface->blit(hand); - } - */ - // primed grenade indicator (animated) - if (item->getFuseTimer() >= 0) - { - const int Pulsate[8] = { 0, 1, 2, 3, 4, 3, 2, 1 }; - Surface *tempSurface = _game->getMod()->getSurfaceSet("SCANG.DAT")->getFrame(6); - int x = (RuleInventory::HAND_W - rule->getInventoryWidth()) * RuleInventory::SLOT_W / 2; - int y = (RuleInventory::HAND_H - rule->getInventoryHeight()) * RuleInventory::SLOT_H / 2; - tempSurface->blitNShade(hand, x, y, Pulsate[_save->getAnimFrame() % 8], false, item->isFuseEnabled() ? 0 : 32); - } + InventoryItemSprite(*item, _save, *hand, item->getInvSpriteBounds()).draw(*surfaceSet, InventorySpriteContext::BATTSCAPE_HAND, animFrame); + InventoryItemSprite(*item, _save, *hand).drawHandOverlay(InventorySpriteContext::BATTSCAPE_HAND, animFrame); } if (drawReactionIndicator) { @@ -1988,8 +1896,8 @@ void BattlescapeState::drawHandsItems() } } } - drawItem(leftHandItem, _btnLeftHandItem, _numAmmoLeft, _numMedikitLeft, _numTwoHandedIndicatorLeft, left); - drawItem(rightHandItem, _btnRightHandItem, _numAmmoRight, _numMedikitRight, _numTwoHandedIndicatorRight, right); + drawItem(leftHandItem, _btnLeftHandItem, left); + drawItem(rightHandItem, _btnRightHandItem, right); } /** @@ -2138,6 +2046,7 @@ void BattlescapeState::updateSoldierInfo(bool checkFOV) crop.blit(_rank); } } + SpriteOverlay(*_rank, _save).draw(*soldier->getArmor(), battleUnit, soldier, _save->getAnimFrame()); } else { @@ -2418,6 +2327,12 @@ void BattlescapeState::animate() blinkVisibleUnitButtons(); blinkHealthBar(); + if(auto unit = _save->getSelectedUnit()) + { + SpriteOverlay(*_rank, _save).draw(*unit->getArmor(), unit, unit->getGeoscapeSoldier(), _save->getAnimFrame()); + } + + if (!_map->getProjectile()) { drawHandsItems(); diff --git a/src/Battlescape/BattlescapeState.h b/src/Battlescape/BattlescapeState.h index ae3096c21c..00073bfe97 100644 --- a/src/Battlescape/BattlescapeState.h +++ b/src/Battlescape/BattlescapeState.h @@ -71,9 +71,6 @@ class BattlescapeState : public State WarningMessage *_warning; Text *_txtName; NumberText *_numTimeUnits, *_numEnergy, *_numHealth, *_numMorale, *_numLayers; - std::vector _numAmmoLeft, _numAmmoRight; - std::vector _numMedikitLeft, _numMedikitRight; - NumberText *_numTwoHandedIndicatorLeft, *_numTwoHandedIndicatorRight; Uint8 _twoHandedRed, _twoHandedGreen; Bar *_barTimeUnits, *_barEnergy, *_barHealth, *_barMorale, *_barMana; bool _manaBarVisible; @@ -103,7 +100,7 @@ class BattlescapeState : public State /// Shifts the red colors of the visible unit buttons backgrounds. void blinkVisibleUnitButtons(); /// Draw hand item with ammo number. - void drawItem(BattleItem *item, Surface *hand, std::vector &ammoText, std::vector &medikitText, NumberText *twoHandedText, bool drawReactionIndicator); + void drawItem(const BattleItem *item, Surface *hand, bool drawReactionIndicator) const; /// Draw both hands sprites. void drawHandsItems(); /// Shifts the colors of the health bar when unit has fatal wounds. diff --git a/src/Battlescape/Inventory.cpp b/src/Battlescape/Inventory.cpp index 270be559a6..846b20e097 100644 --- a/src/Battlescape/Inventory.cpp +++ b/src/Battlescape/Inventory.cpp @@ -47,6 +47,7 @@ #include "../Engine/Screen.h" #include "../Engine/CrossPlatform.h" #include "TileEngine.h" +#include "InventoryItemSprite.h" namespace OpenXcom { @@ -62,18 +63,19 @@ namespace OpenXcom */ Inventory::Inventory(Game *game, int width, int height, int x, int y, bool base) : InteractiveSurface(width, height, x, y), _game(game), _selUnit(0), _selItem(0), _tu(true), _base(base), _mouseOverItem(0), _groundOffset(0), _animFrame(0) { - _twoHandedRed = _game->getMod()->getInterface("battlescape")->getElement("twoHandedRed")->color; - _twoHandedGreen = _game->getMod()->getInterface("battlescape")->getElement("twoHandedGreen")->color; - _depth = _game->getSavedGame()->getSavedBattle()->getDepth(); _grid = new Surface(width, height, 0, 0); _items = new Surface(width, height, 0, 0); _gridLabels = new Surface(width, height, 0, 0); _selection = new Surface(RuleInventory::HAND_W * RuleInventory::SLOT_W, RuleInventory::HAND_H * RuleInventory::SLOT_H, x, y); + _bigObs = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); _warning = new WarningMessage(224, 24, 48, 176); _stackNumber = new NumberText(15, 15, 0, 0); _stackNumber->setBordered(true); + Uint8 stackNumberColor = _game->getMod()->getInterface("inventory")->getElement("numStack")->color; + _stackNumber->setColor(stackNumberColor); + _warning->initText(_game->getMod()->getFont("FONT_BIG"), _game->getMod()->getFont("FONT_SMALL"), _game->getLanguage()); _warning->setColor(_game->getMod()->getInterface("battlescape")->getElement("warning")->color2); _warning->setTextColor(_game->getMod()->getInterface("battlescape")->getElement("warning")->color); @@ -82,11 +84,6 @@ Inventory::Inventory(Game *game, int width, int height, int x, int y, bool base) _animTimer->onTimer((SurfaceHandler)&Inventory::animate); _animTimer->start(); - _stunIndicator = _game->getMod()->getSurface("BigStunIndicator", false); - _woundIndicator = _game->getMod()->getSurface("BigWoundIndicator", false); - _burnIndicator = _game->getMod()->getSurface("BigBurnIndicator", false); - _shockIndicator = _game->getMod()->getSurface("BigShockIndicator", false); - const SavedBattleGame *battleSave = _game->getSavedGame()->getSavedBattle(); if (battleSave) { @@ -95,7 +92,7 @@ Inventory::Inventory(Game *game, int width, int height, int x, int y, bool base) { if (!enviro->getInventoryShockIndicator().empty()) { - _shockIndicator = _game->getMod()->getSurface(enviro->getInventoryShockIndicator(), false); + auto _shockIndicator = _game->getMod()->getSurface(enviro->getInventoryShockIndicator(), false); } } } @@ -298,71 +295,43 @@ void Inventory::drawGridLabels(bool showTuCost) */ void Inventory::drawItems() { - const int Pulsate[8] = { 0, 1, 2, 3, 4, 3, 2, 1 }; const SavedBattleGame* save = _game->getSavedGame()->getSavedBattle(); - Surface *tempSurface = _game->getMod()->getSurfaceSet("SCANG.DAT")->getFrame(6); - auto primers = [&](int x, int y, bool a) - { - tempSurface->blitNShade(_items, x, y, Pulsate[_animFrame % 8], false, a ? 0 : 32); - }; - auto indicators = [&](Surface *surf, int x, int y) - { - surf->blitNShade(_items, x, y, Pulsate[_animFrame % 8]); - }; ScriptWorkerBlit work; _items->clear(); - Uint8 color = _game->getMod()->getInterface("inventory")->getElement("numStack")->color; - Uint8 color2 = _game->getMod()->getInterface("inventory")->getElement("numStack")->color2; - if (_selUnit != 0) + + if (_selUnit != nullptr) { - SurfaceSet *texture = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); // Soldier items - for (auto* invItem : *_selUnit->getInventory()) + for (const auto* invItem : *_selUnit->getInventory()) { - const Surface *frame = invItem->getBigSprite(texture, save, _animFrame); + // if the item is selected (grabbed by the cursor) continue. It isn't handled here. + if (invItem == _selItem) { continue; } - if (invItem == _selItem || !frame) - continue; - - int x, y; - if (invItem->getSlot()->getType() == INV_SLOT) - { - x = (invItem->getSlot()->getX() + invItem->getSlotX() * RuleInventory::SLOT_W); - y = (invItem->getSlot()->getY() + invItem->getSlotY() * RuleInventory::SLOT_H); - } - else if (invItem->getSlot()->getType() == INV_HAND) - { - x = (invItem->getSlot()->getX() + invItem->getRules()->getHandSpriteOffX()); - y = (invItem->getSlot()->getY() + invItem->getRules()->getHandSpriteOffY()); - } - else - { - continue; - } - BattleItem::ScriptFill(&work, invItem, save, BODYPART_ITEM_INVENTORY, _animFrame, 0); - work.executeBlit(frame, _items, x, y, 0); + const auto itemSlot = invItem->getSlot(); + SDL_Rect spriteBounds = invItem->getInvSpriteBounds(); + spriteBounds.x += itemSlot->getX(); + spriteBounds.y += itemSlot->getY(); - // two-handed indicator - if (invItem->getSlot()->getType() == INV_HAND) + if (invItem->getSlot()->getType() != INV_HAND) { - if (invItem->getRules()->isTwoHanded() || invItem->getRules()->isBlockingBothHands()) - { - NumberText text = NumberText(10, 5, 0, 0); - text.setPalette(getPalette()); - text.setColor(invItem->getRules()->isBlockingBothHands() ? _twoHandedRed : _twoHandedGreen); - text.setBordered(false); - text.setX(invItem->getSlot()->getX() + RuleInventory::HAND_W * RuleInventory::SLOT_W - 5); - text.setY(invItem->getSlot()->getY() + RuleInventory::HAND_H * RuleInventory::SLOT_H - 7); - text.setValue(2); - text.blit(_items->getSurface()); - } + // if the cursor is hovering over an item append the hover context. + auto context = _mouseOverItem == invItem ? InventorySpriteContext::SOLDIER_INV_SLOT.with(InventorySpriteContext::CURSOR_HOVER) + : InventorySpriteContext::SOLDIER_INV_SLOT; + InventoryItemSprite(*invItem, save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); } - - // grenade primer indicators - if (invItem->getFuseTimer() >= 0 && invItem->getRules()->getInventoryWidth() > 0) + else // hand slot { - primers(x, y, invItem->isFuseEnabled()); + const auto handSlotBounds = SDL_Rect{ + static_cast(itemSlot->getX() + 1), + static_cast(itemSlot->getY() + 1), + (RuleInventory::HAND_SLOT_W) - 1, + (RuleInventory::HAND_SLOT_H) - 2, + }; + auto context = _mouseOverItem == invItem ? InventorySpriteContext::SOLDIER_INV_HAND.with(InventorySpriteContext::CURSOR_HOVER) + : InventorySpriteContext::SOLDIER_INV_HAND; + InventoryItemSprite(*invItem, save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); + InventoryItemSprite(*invItem, save, *_items, handSlotBounds).drawHandOverlay(context, _animFrame); } } @@ -370,18 +339,19 @@ void Inventory::drawItems() stackLayer.setPalette(getPalette()); // Ground items - int fatalWounds = 0; auto& occupiedSlots = *clearOccupiedSlotsCache(); - for (auto* groundItem : *_selUnit->getTile()->getInventory()) + for (const auto* groundItem : *_selUnit->getTile()->getInventory()) { - const Surface *frame = groundItem->getBigSprite(texture, save, _animFrame); - // note that you can make items invisible by setting their width or height to 0 (for example used with tank corpse items) - if (groundItem == _selItem || groundItem->getRules()->getInventoryHeight() == 0 || groundItem->getRules()->getInventoryWidth() == 0 || !frame) - continue; - - // check if item is in visible range - if (groundItem->getSlotX() < _groundOffset || groundItem->getSlotX() >= _groundOffset + _groundSlotsX) + const auto rules = groundItem->getRules(); + // ground items can have more potentially valid states that need to be filtered. + if (groundItem == _selItem // selected item not handled here. + || rules->getInventoryHeight() == 0 || rules->getInventoryWidth() == 0 // items with no width or height also filtered (tank corpses) + || rules->getBigSprite() == -1 // items with no sprite filtered + || groundItem->getSlotX() < _groundOffset // item out of sight (left side) + || groundItem->getSlotX() >= _groundOffset + _groundSlotsX) // item out of sight (right side) + { continue; + } // check if something was draw here before auto& pos = occupiedSlots[groundItem->getSlotY()][groundItem->getSlotX() - _groundOffset]; @@ -394,57 +364,14 @@ void Inventory::drawItems() pos = true; } - int x, y; - x = (groundItem->getSlot()->getX() + (groundItem->getSlotX() - _groundOffset) * RuleInventory::SLOT_W); - y = (groundItem->getSlot()->getY() + groundItem->getSlotY() * RuleInventory::SLOT_H); - BattleItem::ScriptFill(&work, groundItem, save, BODYPART_ITEM_INVENTORY, _animFrame, 0); - work.executeBlit(frame, _items, x, y, 0); - - // grenade primer indicators - if (groundItem->getFuseTimer() >= 0 && groundItem->getRules()->getInventoryWidth() > 0) - { - primers(x, y, groundItem->isFuseEnabled()); - } + const auto itemSlot = groundItem->getSlot(); + SDL_Rect spriteBounds = groundItem->getInvSpriteBounds(); + spriteBounds.x += itemSlot->getX(); + spriteBounds.y += itemSlot->getY(); - // fatal wounds - fatalWounds = 0; - if (groundItem->getUnit()) - { - // don't show on dead units - if (groundItem->getUnit()->getStatus() == STATUS_UNCONSCIOUS && groundItem->getUnit()->indicatorsAreEnabled()) - { - fatalWounds = groundItem->getUnit()->getFatalWounds(); - if (_burnIndicator && groundItem->getUnit()->getFire() > 0) - { - indicators(_burnIndicator, x, y); - } - else if (_woundIndicator && fatalWounds > 0) - { - indicators(_woundIndicator, x, y); - } - else if (_shockIndicator && groundItem->getUnit()->hasNegativeHealthRegen()) - { - indicators(_shockIndicator, x, y); - } - else if (_stunIndicator) - { - indicators(_stunIndicator, x, y); - } - } - } - if (fatalWounds > 0) - { - _stackNumber->setX((groundItem->getSlot()->getX() + ((groundItem->getSlotX() + groundItem->getRules()->getInventoryWidth()) - _groundOffset) * RuleInventory::SLOT_W)-4); - if (fatalWounds > 9) - { - _stackNumber->setX(_stackNumber->getX()-4); - } - _stackNumber->setY((groundItem->getSlot()->getY() + (groundItem->getSlotY() + groundItem->getRules()->getInventoryHeight()) * RuleInventory::SLOT_H)-6); - _stackNumber->setValue(fatalWounds); - _stackNumber->draw(); - _stackNumber->setColor(color2); - _stackNumber->blit(stackLayer.getSurface()); - } + auto context = _mouseOverItem == groundItem ? InventorySpriteContext::SOLDIER_INV_SLOT.with(InventorySpriteContext::CURSOR_HOVER) + : InventorySpriteContext::SOLDIER_INV_SLOT; + InventoryItemSprite(*groundItem, save, *_items, spriteBounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_GROUND, _animFrame); // item stacking if (_stackLevel[groundItem->getSlotX()][groundItem->getSlotY()] > 1) @@ -457,7 +384,6 @@ void Inventory::drawItems() _stackNumber->setY((groundItem->getSlot()->getY() + (groundItem->getSlotY() + groundItem->getRules()->getInventoryHeight()) * RuleInventory::SLOT_H)-6); _stackNumber->setValue(_stackLevel[groundItem->getSlotX()][groundItem->getSlotY()]); _stackNumber->draw(); - _stackNumber->setColor(color); _stackNumber->blit(stackLayer.getSurface()); } } @@ -474,7 +400,18 @@ void Inventory::drawSelectedItem() if (_selItem) { _selection->clear(); - _selItem->getRules()->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), _selection, _selItem, _game->getSavedGame()->getSavedBattle(), _animFrame); + SDL_Rect bounds = _selItem->getHandCenteredSpriteBounds(); + const auto save = _game->getSavedGame()->getSavedBattle(); + InventoryItemSprite(*_selItem, save, *_selection, bounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); + + auto handSlotBounds = SDL_Rect{ + static_cast(_selection->getX()), + static_cast(_selection->getY()), + RuleInventory::HAND_SLOT_W, + RuleInventory::HAND_SLOT_H, + }; + // this renders no effects by default, but allows for scripting. + InventoryItemSprite(*_selItem, save, *_selection, bounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); } } diff --git a/src/Battlescape/Inventory.h b/src/Battlescape/Inventory.h index 945a20e259..db42ae2d72 100644 --- a/src/Battlescape/Inventory.h +++ b/src/Battlescape/Inventory.h @@ -31,6 +31,7 @@ class WarningMessage; class BattleItem; class BattleUnit; class NumberText; +class SurfaceSet; class Timer; /** @@ -41,8 +42,12 @@ class Inventory : public InteractiveSurface { private: Game *_game; - Surface *_grid, *_items, *_gridLabels, *_selection; - Uint8 _twoHandedRed, _twoHandedGreen; + Surface *_grid, *_items, *_gridLabels; + /// surface for the currently select item, if any. Equal in size to a hand-slot. + Surface *_selection; + /// Surface set containing big objects (inventory sprite pictures). + SurfaceSet* _bigObs = nullptr; + WarningMessage *_warning; BattleUnit *_selUnit; BattleItem *_selItem; @@ -51,7 +56,6 @@ class Inventory : public InteractiveSurface int _groundOffset, _animFrame; std::map > _stackLevel; std::vector> _occupiedSlotsCache; - Surface *_stunIndicator, *_woundIndicator, *_burnIndicator, *_shockIndicator; NumberText *_stackNumber; std::string _searchString; Timer *_animTimer; diff --git a/src/Battlescape/InventoryItemSprite.cpp b/src/Battlescape/InventoryItemSprite.cpp new file mode 100644 index 0000000000..698561cc31 --- /dev/null +++ b/src/Battlescape/InventoryItemSprite.cpp @@ -0,0 +1,289 @@ +/* + * Copyright 2010-2023 OpenXcom Developers. + * + * This file is part of OpenXcom. + * + * OpenXcom 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 3 of the License, or + * (at your option) any later version. + * + * OpenXcom 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 OpenXcom. If not, see . + */ +#include +#include "InventoryItemSprite.h" +#include "SpriteOverlay.h" +#include "../Engine/Surface.h" +#include "../Engine/SurfaceSet.h" +#include "../Engine/Script.h" +#include "../Engine/ScriptBind.h" +#include "../Mod/Mod.h" +#include "../Mod/ModScript.h" +#include "../Mod/RuleItem.h" +#include "../Mod/RuleInventory.h" +#include "../Mod/RuleInterface.h" +#include "../Mod/RuleEnviroEffects.h" +#include "../Savegame/BattleItem.h" +#include "../Savegame/BattleUnit.h" +#include "../Savegame/SavedBattleGame.h" +#include "../Interface/Text.h" +#include "../Interface/NumberText.h" + +namespace OpenXcom +{ + +/** + * @brief Draws the inventory item sprite in the current context. + * @param surfaceSet The set that contains this sprite (should be BIGOBS.PCK). +*/ +void InventoryItemSprite::draw(const SurfaceSet& surfaceSet, InventorySpriteContext context, int animationFrame) +{ + const Surface* sprite = _battleItem->getBigSprite(&surfaceSet, _save, animationFrame); + + if (!sprite) + { + Log(LOG_WARNING) << "Attempted to draw item without sprite. Item: " << _ruleItem.getType(); + return; + } + + BattleItem::ScriptFill(&_scriptWorker, _battleItem, _save, BODYPART_ITEM_INVENTORY, animationFrame, 0); + _scriptWorker.executeBlit(sprite, &_target, _bounds.x, _bounds.y, 0); + + SpriteOverlay(_target, _bounds, _save).draw(_ruleItem, _battleItem, &context, animationFrame); + + if (context.options & InventorySpriteContext::DRAW_GRENADE) { drawGrenadePrimedIndicator(animationFrame); } + if (context.options & InventorySpriteContext::DRAW_FATAL_WOUNDS) { drawFatalWoundIndicator(); } + if (context.options & InventorySpriteContext::DRAW_CORPSE_STATE) { drawCorpseIndicator(animationFrame); } +} + +/** + * @brief Draws the hand-slot overlays, including related scripting. +*/ +void InventoryItemSprite::drawHandOverlay(InventorySpriteContext context, int animationFrame) +{ + context.options = static_cast(context.options | InventorySpriteContext::DRAW_HAND_OVERLAY); + SpriteOverlay(_target, _bounds, _save).draw(_ruleItem, _battleItem, &context, animationFrame); + + if (context.options & InventorySpriteContext::DRAW_AMMO) { drawAmmoIndicator(); } + if (context.options & InventorySpriteContext::DRAW_MEDIKIT) { drawMedkitIndicator(); } + if (context.options & InventorySpriteContext::DRAW_TWOHAND) { drawTwoHandIndicator(); } +} + +namespace // some short helper functions. +{ +/** + * @brief Transforms a number into its position in a triangle wave. + * For example, triangleWave(x, 8, 4) => (0, 1, 2, 3, 4, 3, 2, 1) +*/ +int triangleWave(int number, int period, int amplitude){ return abs((number % period) - amplitude); } + +/// Gets the number of digits in a positive number less than 1000. +int getDigits(int number) { return number < 10 ? 1 : number < 100 ? 2 : 3; } + +std::pair topLeft(const SDL_Rect& bounds, int numW, int numH, int spacing) +{ + return std::pair{bounds.x + spacing, bounds.y + spacing}; +} + +std::pair topRight(const SDL_Rect& bounds, int numW, int numH, int spacing) +{ + return std::pair{bounds.x + bounds.w - numW - spacing, bounds.y + spacing}; +} + +std::pair bottomLeft(const SDL_Rect& bounds, int numW, int numH, int spacing) +{ + return std::pair{bounds.x + spacing, bounds.y + bounds.h - numH - spacing}; +} + +std::pair bottomRight(const SDL_Rect& bounds, int numW, int numH, int spacing) +{ + return std::pair{bounds.x + bounds.w - numW - spacing, bounds.y + bounds.h - numH - spacing}; +} + +/** + * @brief Gets an element member allowing for the fact that the element or the interface might not exist and return null. + * @param member pointer to the member of the element we want. + * @param fallback value to return if the element is not found. + * @return The member of the element, or fallback if the element was not found. +*/ +int getInterfaceElementMember(const Mod& mod, const std::string& interfaceName, const std::string& elementName, int Element::* member, int fallback = 0) +{ + const auto interface = mod.getInterface(interfaceName); + if (interface == nullptr) { return fallback; } + + auto element = interface->getElement(elementName); + if (element == nullptr) { return fallback; } + + return element->*member; +} + +} // namespace + +/** + * @brief Draws a numberText relative to a corner of this overlays bounding box. + * @param getChildCoord Function to get the appropriate corner offset. + * @param spacing Amount of additional space to move from the corner. + * @param number The number to draw (must be positive and less than 1000). + * @param yRowOffset an optional additional y-offset. The number will be rendered at (numHeight + 1) * yRowOffset. + * @param bordered if the number should be bordered or not. +*/ +template +void InventoryItemSprite::drawNumberCorner(const CornerFunc getChildCoord, int spacing, int number, int color, int yRowOffset, bool bordered) +{ + int numW = getDigits(number) * (bordered ? 5 : 4); /// width of number. + int numH = bordered ? 6 : 5; /// height of number. + + auto [x, y] = getChildCoord(_bounds, numW, numH, spacing); + _numberRender.setX(x); + _numberRender.setY(y + yRowOffset * (numH + 1)); + + // avoid resizing if possible. + if (numW > _numberRender.getWidth()) { _numberRender.setWidth(numW); } + if (numH != _numberRender.getHeight()) { _numberRender.setHeight(numH); } + + _numberRender.setColor(color); + _numberRender.setBordered(bordered); + _numberRender.setValue(number); + _numberRender.setPalette(_target.getPalette()); + + _numberRender.blit(_target.getSurface()); +} + +void InventoryItemSprite::drawGrenadePrimedIndicator(int animationFrame) const +{ + if (_battleItem->getFuseTimer() < 0) { return; } + + /// TODO: This should be const, but the get methods are not. + const Surface* primedIndicator = const_cast(_save->getMod())->getSurfaceSet("SCANG.DAT")->getFrame(6); + // if the grenade is primed, but without the fuse enabled, it gets a grey indicator. This is used for flares in XCF. + int newColor = _battleItem->isFuseEnabled() ? 0 : 32; /// TODO: these colors should be moved to the interface. + + primedIndicator->blitNShade(&_target, _bounds.x, _bounds.y, triangleWave(animationFrame, 8, 4), false, newColor); +} + +namespace +{ +Surface* getCorpseStateIndicator(const SavedBattleGame& save, const BattleUnit& unit) { + /// TODO: This should be const, but the get methods are not. + Mod& mod = const_cast(*save.getMod()); + const auto enviro = save.getEnviroEffects(); + + if (unit.getFire() > 0) { return mod.getSurface("BigBurnIndicator", false); } + if (unit.getFatalWounds() > 0) { return mod.getSurface("BigWoundIndicator", false); } + if (unit.hasNegativeHealthRegen()) + { + return enviro && !enviro->getInventoryShockIndicator().empty() ? mod.getSurface(enviro->getInventoryShockIndicator(), false) + : mod.getSurface("BigShockIndicator", false); + } + return mod.getSurface("BigStunIndicator", false); +} +} + +void InventoryItemSprite::drawCorpseIndicator(int animationFrame) const +{ + /// TODO: Implemented to prevent a regression in functionality, but this might be better as a script. + const auto unit = _battleItem->getUnit(); + if (!unit || unit->getStatus() != STATUS_UNCONSCIOUS) { return; } + + if (const Surface* corpseStateIndicator = getCorpseStateIndicator(*_save, *unit)) + { + corpseStateIndicator->blitNShade(&_target, _bounds.x, _bounds.y, triangleWave(animationFrame, 8, 4)); + } +} + +void InventoryItemSprite::drawFatalWoundIndicator() +{ + const auto unit = _battleItem->getUnit(); + if (!unit || unit->getStatus() != STATUS_UNCONSCIOUS) { return; } + + int woundCount = unit->getFatalWounds(); + + /// TODO: this element should be replaced with a more descriptively named element. + int woundColor = getInterfaceElementMember(*_save->getMod(), "inventory", "numStack", &Element::color2); + + drawNumberCorner(bottomRight, 0, woundCount, woundColor, 0, true); +} + +void InventoryItemSprite::drawAmmoIndicator() +{ + int ammoColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numAmmoRight", &Element::color) : + _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numAmmoLeft", &Element::color) + : throw std::logic_error("item in hand with bad hand value."); + + for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) + { + if (_battleItem->isAmmoVisibleForSlot(slot)) + { + const BattleItem* ammo = _battleItem->getAmmoForSlot(slot); + int ammoQuant = ammo ? ammo->getAmmoQuantity() : 0; + + drawNumberCorner(topLeft, 0, ammoQuant, ammoColor, slot); + } + } +} + +void InventoryItemSprite::drawMedkitIndicator() +{ + if (_ruleItem.getBattleType() != BT_MEDIKIT) { return; } + + int medkitColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numMedikitRight", &Element::color) : + _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numMedikitLeft", &Element::color) + : throw std::logic_error("item in hand with bad hand value."); + + drawNumberCorner(bottomLeft, 1, _battleItem->getPainKillerQuantity(), medkitColor, -2); + drawNumberCorner(bottomLeft, 1, _battleItem->getStimulantQuantity(), medkitColor, -1); + drawNumberCorner(bottomLeft, 1, _battleItem->getHealQuantity(), medkitColor, 0); +} + +void InventoryItemSprite::drawTwoHandIndicator() +{ + if (!_ruleItem.isTwoHanded()) { return; } + + int color = _ruleItem.isBlockingBothHands() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "twoHandedRed", &Element::color) + : getInterfaceElementMember(*_save->getMod(), "battlescape", "twoHandedGreen", &Element::color); + + drawNumberCorner(bottomRight, 1, 2, color); +} + +/// Register the constants and options appropriate for InventorySpriteContext in scripting. Be aware this has some limited r/w capabilities. +void InventorySpriteContext::ScriptRegister(ScriptParserBase* parser) +{ + Bind invSpriteContextBinder = { parser }; + + invSpriteContextBinder.addCustomConst("INV_SLOT_W", RuleInventory::SLOT_W); + invSpriteContextBinder.addCustomConst("INV_SLOT_H", RuleInventory::SLOT_H); + invSpriteContextBinder.addCustomConst("INV_HAND_SLOT_COUNT_W", RuleInventory::HAND_W); + invSpriteContextBinder.addCustomConst("INV_HAND_SLOT_COUNT_H", RuleInventory::HAND_H); + invSpriteContextBinder.addCustomConst("INV_HAND_OVERLAY_W", RuleInventory::HAND_SLOT_W); + invSpriteContextBinder.addCustomConst("INV_HAND_OVERLAY_H", RuleInventory::HAND_SLOT_H); + + invSpriteContextBinder.addCustomConst("DRAW_GRENADE_INDICATOR", InventorySpriteContext::DRAW_GRENADE); + invSpriteContextBinder.addCustomConst("DRAW_CORPSE_STATE", InventorySpriteContext::DRAW_CORPSE_STATE); + invSpriteContextBinder.addCustomConst("DRAW_FATAL_WOUNDS", InventorySpriteContext::DRAW_FATAL_WOUNDS); + invSpriteContextBinder.addCustomConst("DRAW_AMMO", InventorySpriteContext::DRAW_AMMO); + invSpriteContextBinder.addCustomConst("DRAW_MEDIKIT", InventorySpriteContext::DRAW_MEDIKIT); + invSpriteContextBinder.addCustomConst("DRAW_TWOHAND_INDICATOR", InventorySpriteContext::DRAW_TWOHAND); + + invSpriteContextBinder.add<&InventorySpriteContextScript::isRenderOptionSet>("isRenderOptionSet", "Gets if a render option is set or not (via bitwise &). (result, option)"); + invSpriteContextBinder.add<&InventorySpriteContextScript::getRenderOptions>("getRenderOptions", "Gets the current render options."); + invSpriteContextBinder.add<&InventorySpriteContextScript::setRenderOptions>("setRenderOptions", "Sets (turns on) a given render option or options."); + invSpriteContextBinder.add<&InventorySpriteContextScript::unsetRenderOptions>("unsetRenderOptions", "Unsets (turns off) a given render option or options."); + + invSpriteContextBinder.addCustomConst("CURSOR_HOVER", InventorySpriteContext::CURSOR_HOVER); + invSpriteContextBinder.addCustomConst("CURSOR_SELECTED", InventorySpriteContext::CURSOR_SELECTED); + invSpriteContextBinder.addCustomConst("INVENTORY_AMMO", InventorySpriteContext::INVENTORY_AMMO); + invSpriteContextBinder.addCustomConst("SCREEN_INVENTORY", InventorySpriteContext::SCREEN_INVENTORY); + invSpriteContextBinder.addCustomConst("SCREEN_ALIEN_INV", InventorySpriteContext::SCREEN_ALIEN_INV); + invSpriteContextBinder.addCustomConst("SCREEN_BATTSCAPE", InventorySpriteContext::SCREEN_BATTSCAPE); + + invSpriteContextBinder.add<&InventorySpriteContextScript::isInContext>("isInContext", "Gets if the overlay is in a given context or not (via bitwise &). (result option)"); + invSpriteContextBinder.add<&InventorySpriteContextScript::getContext>("getContext", "Gets the current render context."); +} + +} diff --git a/src/Battlescape/InventoryItemSprite.h b/src/Battlescape/InventoryItemSprite.h new file mode 100644 index 0000000000..938c1237ff --- /dev/null +++ b/src/Battlescape/InventoryItemSprite.h @@ -0,0 +1,159 @@ +#pragma once +/* + * Copyright 2010-2023 OpenXcom Developers. + * + * This file is part of OpenXcom. + * + * OpenXcom 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 3 of the License, or + * (at your option) any later version. + * + * OpenXcom 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 OpenXcom. If not, see . + */ +#include "../Engine/Script.h" +#include "../Interface/NumberText.h" +#include "../Savegame/BattleItem.h" + +namespace OpenXcom +{ + +class Surface; +class SurfaceSet; +class SavedBattleGame; +class BattleItem; +class RuleItem; +class ScriptParserBase; + +/// Struct associating the sprites render context with its render options. +struct InventorySpriteContext +{ + /// The context in which this class is being rendered. Immutable. + const enum RenderContext : int + { + SCREEN_INVENTORY = 1 << 0, + SCREEN_BATTSCAPE = 1 << 1, + SCREEN_ALIEN_INV = 1 << 2, + SCREEN_UFOPEDIA = 1 << 3, + CURSOR_HOVER = 1 << 4, /// The cursor is hovering over this item in the inventory window. + CURSOR_SELECTED = 1 << 5, /// Item is grabed/selected by the cursor in the inventory window. + INVENTORY_AMMO = 1 << 6, /// Item is being render in the inventory ammo window. + } renderContext; + + /// The options to use when displaying this sprite. Can be changed via scripting. + enum OverlayOptions : int + { + DRAW_NONE = 0, + DRAW_GRENADE = 1 << 0, /// draw the grenade primed indicator + DRAW_CORPSE_STATE = 1 << 1, /// draw the corpse state indicator. Note by default there are no sprites associated with this. + DRAW_FATAL_WOUNDS = 1 << 2, /// draw the number of fatal wounds. + DRAW_AMMO = 1 << 3, /// draw the ammo indicator. + DRAW_MEDIKIT = 1 << 4, /// draw the medikit quantity indicator. + DRAW_TWOHAND = 1 << 5, /// draw the two-hand indicator. + DRAW_HAND_OVERLAY = 1 << 6, /// Item is being rendered with a hand-overlay. (This value should only get set by Sprite Overlay) + DRAW_ALL = INT_MAX, + } options; + + // standard contexts + + static const InventorySpriteContext SOLDIER_INV_HAND; + static const InventorySpriteContext SOLDIER_INV_SLOT; + static const InventorySpriteContext SOLDIER_INV_GROUND; + static const InventorySpriteContext ALIEN_INV_HAND; + static const InventorySpriteContext BATTSCAPE_HAND; + static const InventorySpriteContext SOLDIER_INV_AMMO; + static const InventorySpriteContext SOLDIER_INV_CURSOR; + + /// returns a copy of the object with the new value or-ed in. + InventorySpriteContext with(RenderContext otherContext) const + { + return InventorySpriteContext{ static_cast(renderContext | otherContext), options }; + } + + static constexpr const char* ScriptName = "InvSpriteContext"; + static void ScriptRegister(ScriptParserBase* parser); +}; + +inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_HAND{ RenderContext::SCREEN_INVENTORY, static_cast(DRAW_GRENADE | DRAW_TWOHAND)}; +inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_SLOT{ RenderContext::SCREEN_INVENTORY, OverlayOptions::DRAW_GRENADE}; +inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_GROUND{ RenderContext::SCREEN_INVENTORY, static_cast(DRAW_GRENADE | DRAW_FATAL_WOUNDS | DRAW_CORPSE_STATE )}; +inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_CURSOR{ static_cast(CURSOR_SELECTED | SCREEN_INVENTORY), OverlayOptions::DRAW_NONE}; +inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_AMMO{ RenderContext::SCREEN_INVENTORY, OverlayOptions::DRAW_NONE}; +inline constexpr InventorySpriteContext InventorySpriteContext::ALIEN_INV_HAND{ RenderContext::SCREEN_ALIEN_INV, OverlayOptions::DRAW_NONE}; +inline constexpr InventorySpriteContext InventorySpriteContext::BATTSCAPE_HAND{ RenderContext::SCREEN_BATTSCAPE, static_cast(DRAW_GRENADE | DRAW_AMMO | DRAW_MEDIKIT | DRAW_TWOHAND)}; + +/// Namespace for segregating InventorySpriteContext scripting functions. +namespace InventorySpriteContextScript +{ + inline void getContext(InventorySpriteContext* context, int& result) { result = context->renderContext; } + /// Checks if a given render context is valid for the current context. + inline void isInContext(InventorySpriteContext* context, int& result, int contextOption) { result = contextOption & context->renderContext; } + + typedef InventorySpriteContext::OverlayOptions OverlayOptions; + inline void getRenderOptions(InventorySpriteContext* context, int& result) { result = context->options; } + inline void isRenderOptionSet(InventorySpriteContext* context, int& result, int options) { result = options & context->options; } + inline void setRenderOptions(InventorySpriteContext* context, int options) { context->options = static_cast(context->options | options); } + inline void unsetRenderOptions(InventorySpriteContext* context, int options) { context->options = static_cast(context->options & ~options); } +} + +/** + * @brief Handles rendering of an inventory item sprite, including all scripting. Non-owening and should not leave the local scope. +*/ +class InventoryItemSprite +{ +private: + /// Worker for pixel level script blitting. + ScriptWorkerBlit _scriptWorker{}; + const BattleItem* _battleItem; + const RuleItem& _ruleItem; + const SavedBattleGame* _save; + /// Surface this item should be draw to. + Surface& _target; + /// Bounding box for the sprite. + const SDL_Rect _bounds; + /// Number Text context provided for rendering/scripting. + NumberText _numberRender = NumberText(4, 5); + +public: + /** + * @brief Creates a new inventory item sprite. + * @param target The surface the item is going to be rendered to. + * @param spriteBounds The bounds of the sprite. Not necessarily equal to target's area. + */ + InventoryItemSprite(const BattleItem& battleItem, const SavedBattleGame* save, Surface& target, const SDL_Rect& spriteBounds) + : _battleItem(&battleItem), _ruleItem(*battleItem.getRules()), _save(save), _target(target), _bounds(spriteBounds) {} + + /** + * @brief Creates a new inventory item sprite. Bounds are assumed to be equal to target's dimensions with x and y of 0, 0. + * @param target The surface the item is going to be rendered to. + */ + InventoryItemSprite(const BattleItem& battleItem, const SavedBattleGame* save, Surface& target) + : _battleItem(&battleItem), _ruleItem(*battleItem.getRules()), _save(save), _target(target), + _bounds(SDL_Rect{ 0, 0, static_cast(_target.getWidth()), static_cast(target.getHeight()) }) {} + + + /// Draw the sprite, including scripted recolor and blitting. + void draw(const SurfaceSet& surfaceSet, InventorySpriteContext context, int animationFrame = 0); + /// Draw the hand overlay, which does not include the sprite. + void drawHandOverlay(InventorySpriteContext context, int animationFrame = 0); + +private: + void drawGrenadePrimedIndicator(int animationFrame) const; + void drawAmmoIndicator(); + void drawMedkitIndicator(); + void drawTwoHandIndicator(); + void drawCorpseIndicator(int animationFrame) const; + void drawFatalWoundIndicator(); + + /// Draws a number in a given corner. + template + void drawNumberCorner(CornerFunc getChildCoord, int spacing, int number, int color, int yRowOffset = 0, bool bordered = false); +}; + +} diff --git a/src/Battlescape/InventoryState.cpp b/src/Battlescape/InventoryState.cpp index 1cf16772d1..ec71777333 100644 --- a/src/Battlescape/InventoryState.cpp +++ b/src/Battlescape/InventoryState.cpp @@ -22,6 +22,8 @@ #include "InventoryPersonalState.h" #include #include "Inventory.h" +#include "InventoryItemSprite.h" +#include "SpriteOverlay.h" #include "../Basescape/SoldierArmorState.h" #include "../Basescape/SoldierAvatarState.h" #include "../Engine/Game.h" @@ -528,6 +530,8 @@ void InventoryState::init() armorSurface->blitNShade(_soldier, 0, 0); } } + auto bounds = SDL_Rect{ 0, 0, static_cast(_soldier->getWidth()), static_cast(_soldier->getHeight()) }; + SpriteOverlay(*_soldier, bounds, _game->getSavedGame()->getSavedBattle()).draw(*unit->getArmor(), unit, s); // coming from InventoryLoad window... if (_globalLayoutIndex > -1) @@ -2088,7 +2092,19 @@ void InventoryState::think() r.w -= 2; r.h -= 2; _selAmmo->drawRect(&r, Palette::blockOffset(0)+15); - firstAmmo->getRules()->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), _selAmmo, firstAmmo, _game->getSavedGame()->getSavedBattle(), anim); + + const SDL_Rect spriteBounds = firstAmmo->getHandCenteredSpriteBounds(); + const auto save = _game->getSavedGame()->getSavedBattle(); + const auto surfaceSet = *_game->getMod()->getSurfaceSet("BIGOBS.PCK", anim); + InventoryItemSprite(*firstAmmo, save, *_selAmmo, spriteBounds).draw(surfaceSet, InventorySpriteContext::SOLDIER_INV_AMMO, anim); + + constexpr auto handSlotBounds = SDL_Rect{ + static_cast(1), + static_cast(1), + RuleInventory::HAND_SLOT_W-1, + RuleInventory::HAND_SLOT_H-2, + }; + InventoryItemSprite(*firstAmmo, save, *_selAmmo, handSlotBounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_AMMO, anim); } else { diff --git a/src/Battlescape/SpriteOverlay.cpp b/src/Battlescape/SpriteOverlay.cpp new file mode 100644 index 0000000000..9957790680 --- /dev/null +++ b/src/Battlescape/SpriteOverlay.cpp @@ -0,0 +1,211 @@ +#include "SpriteOverlay.h" +#include "InventoryItemSprite.h" +#include "../Engine/Language.h" +#include "../Engine/Script.h" +#include "../Engine/ScriptBind.h" +#include "../Interface/Text.h" +#include "../Interface/NumberText.h" +#include "../Mod/Armor.h" +#include "../Mod/Mod.h" +#include "../Mod/ModScript.h" +#include "../Mod/RuleInventory.h" +#include "../Savegame/SavedBattleGame.h" +#include "../Savegame/BattleUnit.h" +#include "../Savegame/Soldier.h" + +namespace OpenXcom +{ +/** + * @brief Draws a scripted sprite overlay. + * @tparam Battle The battlescape instance of the object associated with this script. + * @tparam Context Context information to pass in to the script. Mutable. + * @tparam Callback The ModScript struct to use when calling this script + * @tparam Rule The rule object associated with the object this script targets. + * @param animationFrame The current animation frame. Defaults to 0. +*/ +template +void SpriteOverlay::draw(const Rule& rule, const Battle* battle, Context* context, int animationFrame) +{ + ModScript::scriptCallback(&rule, battle, _save, this, context, animationFrame); +} + +/// Draw the paperdoll overlay. +template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, Soldier* soldier, int animationFrame); +/// Draw the unitRank overlay. +template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, Soldier* soldier, int animationFrame); + +/** + * @brief Draws an overlay for an inventory sprite/bigobj. + * @param context Struct containing information about the current rendering context and it's options. R/W by the script. +*/ +void SpriteOverlay::draw(const RuleItem& ruleItem, const BattleItem* battleItem, InventorySpriteContext* context, int animationFrame) +{ + if (context->options & InventorySpriteContext::DRAW_HAND_OVERLAY) { + ModScript::scriptCallback(&ruleItem, battleItem, _save, this, context, animationFrame); + } + else { + ModScript::scriptCallback(&ruleItem, battleItem, _save, this, context, animationFrame); + } +}; + +//// Script binding +namespace SpriteOverlayScript { + +/// Draws a number on the surface the sprite targets. +void drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color) +{ + dest->_numberRender.clear(); + dest->_numberRender.setX(dest->_bounds.x + x); + dest->_numberRender.setY(dest->_bounds.y + y); + + // avoid resizing if possible. + if (width > dest->_numberRender.getWidth()) { dest->_numberRender.setWidth(width); } + if (height != dest->_numberRender.getHeight()) { dest->_numberRender.setWidth(height); } + + dest->_numberRender.setPalette(dest->_target.getPalette()); + dest->_numberRender.setColor(static_cast(color)); + dest->_numberRender.setBordered(false); + dest->_numberRender.setValue(value); + dest->_numberRender.blit(dest->_target.getSurface()); +} + +/// Draws text on on the surface the sprite targets. +void drawText(SpriteOverlay* dest, const std::string& text, int x, int y, int width, int height, int color) +{ + auto surfaceText = Text(width, height, dest->_bounds.x + x, dest->_bounds.y + y); + surfaceText.setPalette(dest->_target.getPalette()); + auto temp = Language(); // note getting the selected language is tough here, and might not even be what is wanted. + const auto mod = dest->_save->getMod(); + surfaceText.initText(mod->getFont("FONT_BIG"), mod->getFont("FONT_SMALL"), &temp); + surfaceText.setSmall(); + surfaceText.setColor(color); + surfaceText.setText(text); + surfaceText.blit(dest->_target.getSurface()); +} + + +void blit(SpriteOverlay* dest, const Surface* source, int x, int y) +{ + source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, 0, false, 0); +} + +void blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2) +{ + const auto crop = GraphSubset({dest->_bounds.x + x1, dest->_bounds.x + x2}, {dest->_bounds.y + y1, dest->_bounds.y + y2}); + source->blitNShade(&dest->_target, dest->_bounds.x, dest->_bounds.y, 0, crop); +} + +void blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2) +{ + const auto crop = GraphSubset({x1, x2}, {y1, y2}); + source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, shade, crop); +} + +void blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade) +{ + source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, shade, false, 0); +} + +void blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor) +{ + source->blitNShade(&dest->_target, dest->_bounds.x, dest->_bounds.y, shade, false, newColor); +} + +void drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color) +{ + dest->_target.drawLine(dest->_bounds.x + x1, dest->_bounds.y + y1, dest->_bounds.x + x2, dest->_bounds.y + y2, color); +} + +void drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color) +{ + dest->_target.drawRect(dest->_bounds.x + x1, dest->_bounds.y + y1, dest->_bounds.x + x2, dest->_bounds.y + y2, color); +} + +void drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color) +{ + dest->_target.drawCircle(dest->_bounds.x + x, dest->_bounds.y + y, radius, color); +} + +std::string debugDisplayScript(const SpriteOverlay* overlay) +{ + if (overlay) + { + std::ostringstream output; + output << " (x:" << overlay->_bounds.x << " y:" << overlay->_bounds.y << " w:" << overlay->_bounds.w << " h:" << overlay->_bounds.h << ")"; + return output.str(); + } + else + { + return "null"; + } +} + +} // SpriteOverlayScript + +void SpriteOverlay::ScriptRegister(ScriptParserBase* parser) +{ + Bind spriteOverlayBinder = { parser }; + + spriteOverlayBinder.add<&SpriteOverlayScript::blit>("blit", "Blits a sprite onto the overlay. (sprite x y)"); + spriteOverlayBinder.add<&SpriteOverlayScript::blitCrop>("blitCrop", "Blits a sprite onto the overlay with a crop. (sprite x y cropX1 cropY1 cropX2 cropY2)"); + spriteOverlayBinder.add<&SpriteOverlayScript::blitShade>("blitShade", "Blits and shades a sprite onto the overlay. (sprite x y shade)"); + spriteOverlayBinder.add<&SpriteOverlayScript::blitShadeRecolor>("blitShadeRecolor", "Blits, shades, and recolors a sprite onto the overlay. (sprite x y shade color)"); + + spriteOverlayBinder.add<&SpriteOverlayScript::drawNumber>("drawNumber", "Draws number on the overlay. (number x y width height color)"); + spriteOverlayBinder.add<&SpriteOverlayScript::drawText>("drawText", "Draws text on the overlay. (text x y width height color"); + + spriteOverlayBinder.add<&SpriteOverlayScript::drawLine>("drawLine", "Draws a line on the overlay. (x1 y1 x2 y2 color)"); + spriteOverlayBinder.add<&SpriteOverlayScript::drawRect>("drawRect", "Draws a rectangle on the overlay. (x1 y1 x2 y2 color)"); + spriteOverlayBinder.add<&SpriteOverlayScript::drawCirc>("drawCirc", "Draws a circle on the overlay. (x y radius color)"); + + spriteOverlayBinder.add<&SpriteOverlay::getWidth>("getWidth", "Gets the width of this overlay."); + spriteOverlayBinder.add<&SpriteOverlay::getHeight>("getHeight", "Gets the height of this overlay."); + + spriteOverlayBinder.addDebugDisplay<&SpriteOverlayScript::debugDisplayScript>(); +} + +/** + * Constructor of inventory sprite overlay script parser. + */ +ModScript::InventorySpriteOverlayParser::InventorySpriteOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) + : ScriptParserEvents{ shared, name, "item", "battle_game", "overlay", "render_context", "anim_frame" } +{ + BindBase bindBase{ this }; + bindBase.addCustomPtr("rules", mod); + + bindBase.parser->registerPointerType(); +} + +/** + * Constructor of hand overlay script parser. + */ +ModScript::HandOverlayParser::HandOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) + : ScriptParserEvents{ shared, name, "item", "battle_game", "overlay", "render_context", "anim_frame" } +{ + BindBase bindBase{ this }; + bindBase.addCustomPtr("rules", mod); + + bindBase.parser->registerPointerType(); +} + +/** + * Constructor of hand overlay script parser. + */ +ModScript::UnitPaperdollOverlayParser::UnitPaperdollOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) + : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "soldier", "anim_frame" } +{ + BindBase bindBase{ this }; + bindBase.addCustomPtr("rules", mod); +} + +/** + * Constructor of hand overlay script parser. + */ +ModScript::UnitRankOverlayParser::UnitRankOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) + : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "soldier", "anim_frame" } +{ + BindBase bindBase{ this }; + bindBase.addCustomPtr("rules", mod); +} + +} diff --git a/src/Battlescape/SpriteOverlay.h b/src/Battlescape/SpriteOverlay.h new file mode 100644 index 0000000000..cfbfc4d7bd --- /dev/null +++ b/src/Battlescape/SpriteOverlay.h @@ -0,0 +1,117 @@ +#pragma once +/* + * Copyright 2010-2023 OpenXcom Developers. + * + * This file is part of OpenXcom. + * + * OpenXcom 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 3 of the License, or + * (at your option) any later version. + * + * OpenXcom 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 OpenXcom. If not, see . + */ +#include "InventoryItemSprite.h" +#include "../Interface/NumberText.h" + +namespace OpenXcom +{ + +class SpriteOverlay; +class Surface; +class ScriptParserBase; +class SavedBattleGame; + +/// Namespace for segregating SpriteOverlay scripting functions. +namespace SpriteOverlayScript +{ +void drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color); +void drawText(SpriteOverlay* dest, const std::string& text, int width, int height, int x, int y, int color); + +void blit(SpriteOverlay* dest, const Surface* source, int x, int y); +void blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2); +void blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2); +void blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade); +void blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor); + +void drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); +void drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); +void drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color); + +std::string debugDisplayScript(const SpriteOverlay* overlay); +} + +/** + * @brief Class for rendering an overlay on top of an existing sprite via scripting. +*/ +class SpriteOverlay +{ +private: + /// Surface this item should be draw to. + Surface& _target; + /// Bounding box for the sprite. + const SDL_Rect _bounds; + /// Battlescape Instance we are working with. + const SavedBattleGame* _save; + /// Number Text context provided for rendering/scripting. + NumberText _numberRender = NumberText(4, 5); + +public: + /** + * @brief Creates a new InventoryItemSprite should not be stored or persist beyond the local scope. + * @param target The surface the sprite is render to. + * @param spriteBounds The bounds the sprite and scripting should work with, relative to target. The sprite is rendered at x, y of this rect, and that coordinate is treated as 0,0 for scripting. + */ + SpriteOverlay(Surface& target, const SDL_Rect& spriteBounds, const SavedBattleGame *save) + : _target(target), _bounds(spriteBounds), _save(save) {} + /** + * @brief Creates a new InventoryItemSprite should not be stored or persist beyond the local scope. + * @param target The surface the sprite is rendered to. The bounds are infered to be the size of this surface, with a x,y of 0, 0. + */ + SpriteOverlay(Surface& target, const SavedBattleGame* save) + : _target(target), _bounds(SDL_Rect{ 0, 0, static_cast(_target.getWidth()), static_cast(_target.getHeight()) }), _save(save) { } + + // no copy or move. + SpriteOverlay(SpriteOverlay&) = delete; + SpriteOverlay& operator=(SpriteOverlay&) = delete; + + /// Draw the scripted overlay. + template + void draw(const Rule& rule, const Battle* battle, Context* context, int animationFrame = 0); + + void draw(const RuleItem& ruleItem, const BattleItem* battleItem, InventorySpriteContext* context, int animationFrame); + + // these methods are exposed for scripting. + + /// Gets the width of this overlay. + [[nodiscard]] int getWidth() const { return _bounds.w; } + /// Gets the height of this overlay. + [[nodiscard]] int getHeight() const { return _bounds.h; } + + friend void SpriteOverlayScript::drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color); + friend void SpriteOverlayScript::drawText(SpriteOverlay* dest, const std::string& text, int x, int y, int width, int height, int color); + + friend void SpriteOverlayScript::blit(SpriteOverlay* dest, const Surface* source, int x, int y); + friend void SpriteOverlayScript::blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2); + friend void SpriteOverlayScript::blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2); + friend void SpriteOverlayScript::blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade); + friend void SpriteOverlayScript::blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor); + friend void SpriteOverlayScript::drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); + friend void SpriteOverlayScript::drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); + friend void SpriteOverlayScript::drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color); + + friend std::string SpriteOverlayScript::debugDisplayScript(const SpriteOverlay* overlay); + + /// Name of class used in script. + static constexpr const char* ScriptName = "SpriteOverlay"; + /// Register all useful function used by script. + static void ScriptRegister(ScriptParserBase* parser); +}; + +} diff --git a/src/Engine/Surface.h b/src/Engine/Surface.h index eb4a2bbafc..dfe66c4393 100644 --- a/src/Engine/Surface.h +++ b/src/Engine/Surface.h @@ -30,7 +30,6 @@ namespace OpenXcom class Font; class Language; -class ScriptWorkerBase; class SurfaceCrop; template class SurfaceRaw; diff --git a/src/Mod/Mod.cpp b/src/Mod/Mod.cpp index c3a5a6be4e..16d272799d 100644 --- a/src/Mod/Mod.cpp +++ b/src/Mod/Mod.cpp @@ -6323,6 +6323,45 @@ void getInventoryScript(const Mod* mod, const RuleInventory* &inv, const std::st } } +/** + * @brief Custom method for retrieving a sprite by set for scripting. + * @param surface The surface the sprite is loaded into. nullptr on error. + */ +void getSpiteScript(const Mod* mod, const Surface*& surface, const std::string& setName, int index) +{ + surface = mod ? const_cast(mod)->getSurfaceSet(setName, false)->getFrame(index) : nullptr; +} + +/** + * @brief Custom method for retrieving a sprite by name for scripting. + * @param surface The surface the sprite is loaded into. nullptr on error. + */ +void getNamedSpriteScript(const Mod* mod, const Surface*& surface, const std::string& spriteName) +{ + surface = mod ? const_cast(mod)->getSurface(spriteName, false) : nullptr; +} + +/** + * @brief Custom method for retrieving an interface element color. + * @param color Out reference for the color. -1 on error. +*/ +void getInterfaceElementColor(const Mod* mod, int &color, const std::string& interfaceName, const std::string& elementName) +{ + auto interface = mod->getInterface(interfaceName); + auto element = interface->getElement(elementName); + color = (interface && element) ? element->color : -1; +} +/** + * @brief Custom method for retrieving an interface element color2. + * @param color Out reference for the color. -1 on error. + */ +void getInterfaceElementColor2(const Mod* mod, int& color, const std::string& interfaceName, const std::string& elementName) +{ + auto interface = mod->getInterface(interfaceName); + auto element = interface->getElement(elementName); + color = (interface && element) ? element->color2 : -1; +} + } // namespace /** @@ -6338,6 +6377,18 @@ void Mod::ScriptRegister(ScriptParserBase *parser) parser->registerPointerType(); parser->registerPointerType(); + { + // since all user generated references to surfaces should be constant, we will call them sprite. + const std::string name = "Sprite"; + parser->registerRawPointerType(name); + Bind surfaceBinder = {parser, name}; + + surfaceBinder.add<&Surface::getWidth>("getWidth", "Get's the width of the sprite. (width)"); + surfaceBinder.add<&Surface::getHeight>("getHeight", "Get's the width of the sprite. (height)"); + +// surfaceBinder.addDebugDisplay<&debugDisplayScript>(); + } + Bind mod = { parser }; mod.add<&offset<&Mod::_soundOffsetBattle>>("getSoundOffsetBattle", "convert mod sound index in first argument to runtime index in given set, second argument is mod id"); @@ -6365,6 +6416,11 @@ void Mod::ScriptRegister(ScriptParserBase *parser) mod.add<&Mod::getInventoryBelt>("getRuleInventoryBelt"); mod.add<&Mod::getInventoryGround>("getRuleInventoryGround"); + mod.add<&getSpiteScript>("getSpriteFromSet", "Gets a sprite identified by set name and index from the appropriate store. (sprite set index"); + mod.add<&getNamedSpriteScript>("getNamedSprite", "Get a sprite identified by a string. (sprite spriteName)"); + mod.add<&getInterfaceElementColor>("getInterfaceElementColor", "Gets the color of a specific interface element. -1 on error. (color interface element)"); + mod.add<&getInterfaceElementColor2>("getInterfaceElementColor2", "Gets the color of a specific interface element. -1 on error. (color interface element)"); + mod.addScriptValue<&Mod::_scriptGlobal, &ModScriptGlobal::getScriptValues>(); } diff --git a/src/Mod/ModScript.h b/src/Mod/ModScript.h index a35517ea47..848afcd6f5 100644 --- a/src/Mod/ModScript.h +++ b/src/Mod/ModScript.h @@ -54,6 +54,8 @@ class Mod; class BattleUnit; class BattleUnitVisibility; class BattleItem; +class SpriteOverlay; +struct InventorySpriteContext; struct StatAdjustment; class Ufo; @@ -92,6 +94,14 @@ class ModScript { RecolorUnitParser(ScriptGlobal* shared, const std::string& name, Mod* mod); }; + struct UnitPaperdollOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, const Soldier*, int> + { + UnitPaperdollOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); + }; + struct UnitRankOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, const Soldier*, int> + { + UnitRankOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); + }; struct SelectUnitParser : ScriptParserEvents { SelectUnitParser(ScriptGlobal* shared, const std::string& name, Mod* mod); @@ -181,6 +191,14 @@ class ModScript { SelectItemParser(ScriptGlobal* shared, const std::string& name, Mod* mod); }; + struct InventorySpriteOverlayParser : ScriptParserEvents, const BattleItem*, const SavedBattleGame*, SpriteOverlay*, InventorySpriteContext*, int> + { + InventorySpriteOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); + }; + struct HandOverlayParser : ScriptParserEvents, const BattleItem*, const SavedBattleGame*, SpriteOverlay*, InventorySpriteContext*, int> + { + HandOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); + }; struct TryPsiAttackItemParser : ScriptParserEvents, const BattleItem*, const BattleUnit*, const BattleUnit*, const RuleSkill*, int, int, int, RNG::RandomState*, int, int, const SavedBattleGame*> { @@ -291,6 +309,8 @@ class ModScript using RecolorUnitSprite = MACRO_NAMED_SCRIPT("recolorUnitSprite", RecolorUnitParser); using SelectUnitSprite = MACRO_NAMED_SCRIPT("selectUnitSprite", SelectUnitParser); + using UnitPaperdollOverlay = MACRO_NAMED_SCRIPT("unitPaperdollOverlay", UnitPaperdollOverlayParser); + using UnitRankOverlay = MACRO_NAMED_SCRIPT("unitRankOverlay", UnitRankOverlayParser); using SelectMoveSoundUnit = MACRO_NAMED_SCRIPT("selectMoveSoundUnit", SelectMoveSoundUnitParser); using ReactionUnitAction = MACRO_NAMED_SCRIPT("reactionUnitAction", ReactionUnitParser); @@ -319,6 +339,8 @@ class ModScript using RecolorItemSprite = MACRO_NAMED_SCRIPT("recolorItemSprite", RecolorItemParser); using SelectItemSprite = MACRO_NAMED_SCRIPT("selectItemSprite", SelectItemParser); + using InventorySpriteOverlay = MACRO_NAMED_SCRIPT("inventorySpriteOverlay", InventorySpriteOverlayParser); + using HandOverlay = MACRO_NAMED_SCRIPT("handOverlay", HandOverlayParser); using ReactionWeaponAction = MACRO_NAMED_SCRIPT("reactionWeaponAction", ReactionUnitParser); @@ -394,6 +416,8 @@ class ModScript using BattleUnitScripts = ScriptGroupgetBigSprite(texture, save, animFrame); - if (frame) - { - ScriptWorkerBlit scr; - BattleItem::ScriptFill(&scr, item, save, BODYPART_ITEM_INVENTORY, animFrame, 0); - scr.executeBlit(frame, surface, this->getHandSpriteOffX(), this->getHandSpriteOffY(), 0); - } - } - else - { - frame = texture->getFrame(this->getBigSprite()); - frame->blitNShade(surface, this->getHandSpriteOffX(), this->getHandSpriteOffY()); - } + const Surface* frame = texture->getFrame(this->getBigSprite()); + frame->blitNShade(surface, this->getHandSpriteOffX(), this->getHandSpriteOffY()); } /** @@ -2681,6 +2666,16 @@ void getBattleTypeScript(const RuleItem *ri, int &ret) ret = (int)BT_NONE; } +void getMedikitTypeScript(const RuleItem* ri, int& ret) +{ + if (ri) + { + ret = static_cast(ri->getMediKitType()); + return; + } + ret = -1; +} + void isSingleTargetScript(const RuleItem* r, int &ret) { if (r) @@ -2839,6 +2834,11 @@ void RuleItem::ScriptRegister(ScriptParserBase* parser) ri.addCustomConst("BT_FLARE", BT_FLARE); ri.addCustomConst("BT_CORPSE", BT_CORPSE); + ri.addCustomConst("BMT_NORMAL", BMT_NORMAL); + ri.addCustomConst("BMT_HEAL", BMT_HEAL); + ri.addCustomConst("BMT_PAINKILER", BMT_PAINKILLER); + ri.addCustomConst("BMT_STIMULANT", BMT_STIMULANT); + ri.add<&getTypeScript>("getType"); ri.add<&RuleItem::getAccuracyAimed>("getAccuracyAimed"); @@ -2857,6 +2857,14 @@ void RuleItem::ScriptRegister(ScriptParserBase* parser) ri.add<&RuleItem::getArmor>("getArmorValue"); ri.add<&RuleItem::getWeight>("getWeight"); + ri.add<&RuleItem::getClipSize>("getClipSize"); + ri.add<&getMedikitTypeScript>("getMediKitType", "Gets the the medikit type. WARNING: BMT_NORMAL if not a medikit."); + ri.add<&RuleItem::getHealQuantity>("getMaxHealQuantity", "Gets the default number of heal charges."); + ri.add<&RuleItem::getPainKillerQuantity>("getMaxPainKillerQuantity", "Gets the default number of painkiller charges."); + ri.add<&RuleItem::getStimulantQuantity>("getMaxStimulantQuantity", "Gets the default number of stim charges."); + ri.add<&RuleItem::getInventoryWidth>("getInvWidth", "Gets an items inventory width."); + ri.add<&RuleItem::getInventoryHeight>("getInvHeight", "Gets an items inventory height."); + ri.add<&RuleItem::getBigSprite>("getBigSpriteIndex", "Gets the index of the BIGOBS sprite used to display this item (without scripting)."); ri.add<&getBattleTypeScript>("getBattleType"); ri.add<&RuleItem::getWaypoints>("getWaypoints"); ri.add<&RuleItem::isWaterOnly>("isWaterOnly"); diff --git a/src/Mod/RuleItem.h b/src/Mod/RuleItem.h index 9cea9db212..3f170422e4 100644 --- a/src/Mod/RuleItem.h +++ b/src/Mod/RuleItem.h @@ -753,7 +753,7 @@ class RuleItem /// Gets the chance of special effect like zombify or corpse explosion or mine triggering. int getSpecialChance() const; /// Draws the item's hand sprite onto a surface. - void drawHandSprite(const SurfaceSet *texture, Surface *surface, const BattleItem *item = 0, const SavedBattleGame* save = 0, int animFrame = 0) const; + void drawHandSprite(const SurfaceSet *texture, Surface *surface) const; /// item's hand spite x offset int getHandSpriteOffX() const; /// item's hand spite y offset diff --git a/src/OpenXcom.2010.vcxproj b/src/OpenXcom.2010.vcxproj index 377bb45827..5898a27649 100644 --- a/src/OpenXcom.2010.vcxproj +++ b/src/OpenXcom.2010.vcxproj @@ -449,6 +449,7 @@ + @@ -582,6 +583,7 @@ + @@ -846,6 +848,7 @@ + @@ -983,6 +986,7 @@ + @@ -1019,4 +1023,4 @@ - + \ No newline at end of file diff --git a/src/Savegame/BattleItem.cpp b/src/Savegame/BattleItem.cpp index 3bc198945e..3e3eba6460 100644 --- a/src/Savegame/BattleItem.cpp +++ b/src/Savegame/BattleItem.cpp @@ -740,6 +740,68 @@ const Surface *BattleItem::getBigSprite(const SurfaceSet *set, const SavedBattle return nullptr; } } +/** + * @brief Gets the items inventory sprite bounds, taking into account the items position within it's container, + * but not the position of the container (the boundary is relative to the container). + * @param groundOffset The number of inventory units the ground container is offset from 0, if any. + * @return A rectangle that describes the inventory sprite's bounds within it's container. +*/ +SDL_Rect BattleItem::getInvSpriteBounds(int groundOffset) const +{ + // item dimensions in inventory units. + int invSlotW = _rules->getInventoryWidth(); + int invSlotH = _rules->getInventoryHeight(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = invSlotW * RuleInventory::SLOT_W; + Uint16 itemH = invSlotH * RuleInventory::SLOT_H; + + // determine the amount the box needs to be offset according to + Sint16 itemX, itemY; + switch (_inventorySlot->getType()) + { + case INV_SLOT: + // position item by place in inventory container * slot size. + itemX = _inventoryX * RuleInventory::SLOT_W; + itemY = _inventoryY * RuleInventory::SLOT_H; + break; + case INV_HAND: + // position item by half the difference in item size and hand slot size in order to center. + itemX = (RuleInventory::HAND_W - invSlotW) * RuleInventory::SLOT_W / 2; + itemY = (RuleInventory::HAND_H - invSlotH) * RuleInventory::SLOT_H / 2; + break; + case INV_GROUND: + // position by place in the ground container, after taking into account the ground offset (in inventory units) + itemX = (_inventoryX - groundOffset) * RuleInventory::SLOT_W; + itemY = _inventoryY * RuleInventory::SLOT_H; + break; + default: + throw std::logic_error("Item: " + _rules->getType() + " in inventory with bad enum value for slotType: " + std::to_string(_inventorySlot->getType())); + } + + return SDL_Rect{itemX, itemY, itemW, itemH}; +} + +/** + * @brief Gets the items inventory sprite bounds relative to a handslot container, assuming that it is to be centered. + * @return A rectangle that describes the inventory sprite's bounds within a hand-slot sized container. +*/ +SDL_Rect BattleItem::getHandCenteredSpriteBounds() const +{ + // item dimensions in inventory units. + int invSlotW = _rules->getInventoryWidth(); + int invSlotH = _rules->getInventoryHeight(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = invSlotW * RuleInventory::SLOT_W; + Uint16 itemH = invSlotH * RuleInventory::SLOT_H; + + // position item by half the difference in item size and hand slot size in order to center. + Sint16 itemX = (RuleInventory::HAND_W - invSlotW) * RuleInventory::SLOT_W / 2; + Sint16 itemY = (RuleInventory::HAND_H - invSlotH) * RuleInventory::SLOT_H / 2; + + return SDL_Rect{itemX, itemY, itemW, itemH}; +} /** * Check if item use any ammo. @@ -1509,6 +1571,7 @@ void BattleItem::ScriptRegister(ScriptParserBase* parser) bi.addRules("getRuleItem"); bi.addPair("getBattleUnit"); + bi.add<&BattleItem::isAmmoVisibleForSlot>("isAmmoVisibleForSlot", "Shows if ammo for a given slot is visible or not. (result slot)"); bi.addFunc("getAmmoItem"); bi.addFunc("getAmmoItem"); bi.addFunc("getAmmoForSlot"); diff --git a/src/Savegame/BattleItem.h b/src/Savegame/BattleItem.h index 25555af0ad..2c394afd86 100644 --- a/src/Savegame/BattleItem.h +++ b/src/Savegame/BattleItem.h @@ -149,6 +149,10 @@ class BattleItem const Surface *getFloorSprite(const SurfaceSet *set, const SavedBattleGame *save, int animFrame, int shade) const; /// Gets the item's inventory sprite. const Surface *getBigSprite(const SurfaceSet *set, const SavedBattleGame *save, int animFrame) const; + /// Gets the bounds of the inventory sprite. + [[nodiscard]] SDL_Rect getInvSpriteBounds(int groundOffset = 0) const; + /// Get the bounds of an item centered in a hand slot. + [[nodiscard]] SDL_Rect getHandCenteredSpriteBounds() const; /// Check if item can use any ammo. bool isWeaponWithAmmo() const; diff --git a/src/Savegame/BattleUnit.cpp b/src/Savegame/BattleUnit.cpp index 72b02dea94..d13a1971fb 100644 --- a/src/Savegame/BattleUnit.cpp +++ b/src/Savegame/BattleUnit.cpp @@ -5846,6 +5846,10 @@ void setFireScript(BattleUnit *bu, int val) } } +void getStatusScript(const BattleUnit* bu, int& status) +{ + status = bu ? bu->getStatus() : status; +} void getVisibleUnitsCountScript(BattleUnit *bu, int &ret) { @@ -6142,6 +6146,7 @@ void BattleUnit::ScriptRegister(ScriptParserBase* parser) bu.add<&BattleUnit::getAggression>("getAggression"); bu.add<&BattleUnit::getTurretDirection>("getTurretDirection"); bu.add<&BattleUnit::getWalkingPhase>("getWalkingPhase"); + bu.add<&BattleUnit::indicatorsAreEnabled>("indicatorsAreEnabled", "Checks if indicators are enabled."); bu.add<&BattleUnit::disableIndicators>("disableIndicators"); bu.add<&BattleUnit::getVisible>("isVisible"); @@ -6188,10 +6193,11 @@ void BattleUnit::ScriptRegister(ScriptParserBase* parser) bu.add<&setBaseStatRangeScript<&BattleUnit::_morale, 0, 100>>("setMorale"); bu.add<&addBaseStatRangeScript<&BattleUnit::_morale, 0, 100>>("addMorale"); + bu.add<&getStatusScript>("getStatus", "Gets the units current status (STATUS_UNCONSCIOUS, STATUS_DEAD...)"); bu.add<&BattleUnit::getFire>("getFire"); bu.add<&setFireScript>("setFire"); - + bu.add<&BattleUnit::hasNegativeHealthRegen>("hasNegativeHealthRegen", "Is the unit's health regeneration negative (in shock)?"); bu.add<&setArmorValueScript>("setArmor", "first arg is side, second one is new value of armor"); bu.add<&addArmorValueScript>("addArmor", "first arg is side, second one is value to add to armor"); @@ -6206,10 +6212,8 @@ void BattleUnit::ScriptRegister(ScriptParserBase* parser) UnitStats::addGetStatsScript<&BattleUnit::_stats>(bu, "Stats."); UnitStats::addSetStatsWithCurrScript<&BattleUnit::_stats, &BattleUnit::_tu, &BattleUnit::_energy, &BattleUnit::_health, &BattleUnit::_mana>(bu, "Stats."); - UnitStats::addGetStatsScript<&BattleUnit::_exp>(bu, "Exp.", true); - bu.add<&getMovmentTypeScript>("getMovmentType", BindBase::functionInvisible); //old bugged name bu.add<&getMovmentTypeScript>("getMovementType", "get move type of unit"); bu.add<&getOriginalMovmentTypeScript>("getOriginalMovementType", "get original move type of unit"); @@ -6296,6 +6300,17 @@ void BattleUnit::ScriptRegister(ScriptParserBase* parser) bu.addCustomConst("COLOR_X1_SILVER", 14); bu.addCustomConst("COLOR_X1_SPECIAL", 15); + bu.addCustomConst("STATUS_STANDING", STATUS_STANDING); + bu.addCustomConst("STATUS_WALKING", STATUS_WALKING); + bu.addCustomConst("STATUS_FLYING", STATUS_FLYING); + bu.addCustomConst("STATUS_TURNING", STATUS_TURNING); + bu.addCustomConst("STATUS_AIMING", STATUS_AIMING); + bu.addCustomConst("STATUS_COLLAPSING", STATUS_COLLAPSING); + bu.addCustomConst("STATUS_DEAD", STATUS_DEAD); + bu.addCustomConst("STATUS_UNCONSCIOUS", STATUS_UNCONSCIOUS); + bu.addCustomConst("STATUS_PANICKING", STATUS_PANICKING); + bu.addCustomConst("STATUS_BERSERK", STATUS_BERSERK); + bu.addCustomConst("STATUS_IGNORE_ME", STATUS_IGNORE_ME); bu.addCustomConst("LOOK_BLONDE", LOOK_BLONDE); bu.addCustomConst("LOOK_BROWNHAIR", LOOK_BROWNHAIR); diff --git a/src/Savegame/SavedBattleGame.cpp b/src/Savegame/SavedBattleGame.cpp index c910de6937..5ad9b6e41c 100644 --- a/src/Savegame/SavedBattleGame.cpp +++ b/src/Savegame/SavedBattleGame.cpp @@ -1944,7 +1944,7 @@ BattleItem *SavedBattleGame::createItemForUnit(const RuleItem *rule, BattleUnit { return nullptr; } - + BattleItem *item = new BattleItem(rule, getCurrentItemId()); if (!unit->addItem(item, _rule, false, fixedWeapon, fixedWeapon)) { @@ -3493,6 +3493,16 @@ void getTileScript(const SavedBattleGame* sbg, const Tile*& t, int x, int y, int } } +void getEnvrionmentShockIndicator(const SavedBattleGame* sbg, const Surface*& shockIndicator) +{ + /// TODO: Should there be a default environment that sets this? + // With scripting there are better ways to handle this in any case (like a tag). + auto* enviro = sbg->getEnviroEffects(); + auto* mod = const_cast(sbg->getMod()); + shockIndicator = enviro && !enviro->getInventoryShockIndicator().empty() ? mod->getSurface(enviro->getInventoryShockIndicator(), false) + : mod->getSurface("BigShockIndicator", false); +} + void setAlienItemLevelScript(SavedBattleGame* sbg, int val) { if (sbg) @@ -3541,7 +3551,7 @@ std::string debugDisplayScript(const SavedBattleGame* p) } // namespace /** - * Register Armor in script parser. + * Register Save Battle Game in script parser. * @param parser Script parser. */ void SavedBattleGame::ScriptRegister(ScriptParserBase* parser) @@ -3560,6 +3570,8 @@ void SavedBattleGame::ScriptRegister(ScriptParserBase* parser) sbg.add<&SavedBattleGame::getReinforcementsItemLevel>("getReinforcementsItemLevel"); sbg.add<&setReinforcementsItemLevelScript>("setReinforcementsItemLevel"); + sbg.add<&getEnvrionmentShockIndicator>("getEnvrionmentShockIndicator", "Gets the shock indicator appropriate for the environment (IE drowning for underwater), or the default if none."); + sbg.addPair("getGeoscapeGame"); sbg.add("flashMessage"); From a01ae51abc4d277af7ae66c26dfe86be4420f1ed Mon Sep 17 00:00:00 2001 From: Austin Stanley Date: Fri, 21 Apr 2023 09:50:17 -0500 Subject: [PATCH 2/3] Requested changes, also InventoryOverlay handles all inventory sprite drawing now. --- src/Battlescape/AlienInventory.cpp | 6 +- src/Battlescape/BattlescapeState.cpp | 10 +- src/Battlescape/Inventory.cpp | 33 +++--- src/Battlescape/InventoryItemSprite.cpp | 146 ++++++++++++++++++------ src/Battlescape/InventoryItemSprite.h | 47 ++++++-- src/Battlescape/InventoryState.cpp | 22 ++-- src/Battlescape/SpriteOverlay.cpp | 126 ++++++++++---------- src/Battlescape/SpriteOverlay.h | 54 ++++----- src/Mod/Mod.cpp | 46 ++++++-- src/Mod/Mod.h | 6 + src/Mod/ModScript.h | 4 +- src/Mod/RuleItem.cpp | 29 ----- src/Mod/RuleItem.h | 6 - src/Savegame/BattleItem.cpp | 97 ---------------- src/Savegame/BattleItem.h | 6 - src/Savegame/BattleUnit.cpp | 2 +- src/Savegame/SavedBattleGame.cpp | 12 -- src/Ufopaedia/ArticleStateItem.cpp | 11 +- 18 files changed, 334 insertions(+), 329 deletions(-) diff --git a/src/Battlescape/AlienInventory.cpp b/src/Battlescape/AlienInventory.cpp index 1ebde6f0a0..67db64833d 100644 --- a/src/Battlescape/AlienInventory.cpp +++ b/src/Battlescape/AlienInventory.cpp @@ -166,7 +166,7 @@ void AlienInventory::drawItems() const if (item->getSlot()->getType() != INV_HAND) { continue; } const auto handSlot = item->getSlot(); - SDL_Rect spriteBounds = item->getInvSpriteBounds(); + SDL_Rect spriteBounds = InventoryItemSprite::getHandCenteredSpriteBounds(*item); spriteBounds.x += handSlot->getX() + _game->getMod()->getAlienInventoryOffsetX(); spriteBounds.y += handSlot->getY(); @@ -175,7 +175,7 @@ void AlienInventory::drawItems() const handSlot->isLeftHand() ? _dynamicOffset : throw std::logic_error("Item in hand slot with bad hand value.")); - InventoryItemSprite(*item, save, *_items, spriteBounds).draw(*surfaceSet, InventorySpriteContext::ALIEN_INV_HAND, _animFrame); + InventoryItemSprite(*item, *save, *_items, spriteBounds).draw(*surfaceSet, InventorySpriteContext::ALIEN_INV_HAND, _animFrame); /// offset for hand overlay auto handSlotBounds = SDL_Rect{ @@ -185,7 +185,7 @@ void AlienInventory::drawItems() const RuleInventory::HAND_SLOT_H-2, }; // this should render no default effects, but allows for scripting. - InventoryItemSprite(*item, save, *_items, handSlotBounds).drawHandOverlay(InventorySpriteContext::ALIEN_INV_HAND, _animFrame); + InventoryItemSprite(*item, *save, *_items, handSlotBounds).drawHandOverlay(InventorySpriteContext::ALIEN_INV_HAND, _animFrame); } } } diff --git a/src/Battlescape/BattlescapeState.cpp b/src/Battlescape/BattlescapeState.cpp index 3fe36e6ad9..d0a1929ea8 100644 --- a/src/Battlescape/BattlescapeState.cpp +++ b/src/Battlescape/BattlescapeState.cpp @@ -1847,8 +1847,8 @@ void BattlescapeState::drawItem(const BattleItem* item, Surface* hand, bool draw const SurfaceSet* surfaceSet = _game->getMod()->getSurfaceSet("BIGOBS.PCK"); int animFrame = _save->getAnimFrame(); - InventoryItemSprite(*item, _save, *hand, item->getInvSpriteBounds()).draw(*surfaceSet, InventorySpriteContext::BATTSCAPE_HAND, animFrame); - InventoryItemSprite(*item, _save, *hand).drawHandOverlay(InventorySpriteContext::BATTSCAPE_HAND, animFrame); + InventoryItemSprite(*item, *_save, *hand, InventoryItemSprite::getHandCenteredSpriteBounds(*item)).draw(*surfaceSet, InventorySpriteContext::BATTSCAPE_HAND, animFrame); + InventoryItemSprite(*item, *_save, *hand, SpriteOverlay::getSurfaceBounds(*hand)).drawHandOverlay(InventorySpriteContext::BATTSCAPE_HAND, animFrame); } if (drawReactionIndicator) { @@ -2046,7 +2046,8 @@ void BattlescapeState::updateSoldierInfo(bool checkFOV) crop.blit(_rank); } } - SpriteOverlay(*_rank, _save).draw(*soldier->getArmor(), battleUnit, soldier, _save->getAnimFrame()); + auto overlay = SpriteOverlay(*_rank, SpriteOverlay::getSurfaceBounds(*_rank), _save); + overlay.draw(*soldier->getArmor(), battleUnit, _save->getAnimFrame()); } else { @@ -2329,7 +2330,8 @@ void BattlescapeState::animate() if(auto unit = _save->getSelectedUnit()) { - SpriteOverlay(*_rank, _save).draw(*unit->getArmor(), unit, unit->getGeoscapeSoldier(), _save->getAnimFrame()); + auto overlay = SpriteOverlay(*_rank, SpriteOverlay::getSurfaceBounds(*_rank), _save); + overlay.draw(*unit->getArmor(), unit, _save->getAnimFrame()); } diff --git a/src/Battlescape/Inventory.cpp b/src/Battlescape/Inventory.cpp index 846b20e097..086a66f8eb 100644 --- a/src/Battlescape/Inventory.cpp +++ b/src/Battlescape/Inventory.cpp @@ -309,20 +309,27 @@ void Inventory::drawItems() if (invItem == _selItem) { continue; } const auto itemSlot = invItem->getSlot(); - SDL_Rect spriteBounds = invItem->getInvSpriteBounds(); - spriteBounds.x += itemSlot->getX(); - spriteBounds.y += itemSlot->getY(); - if (invItem->getSlot()->getType() != INV_HAND) + if (itemSlot->getType() != INV_HAND) // not hand slot { + SDL_Rect spriteBounds = InventoryItemSprite::getInvSpriteBounds(*invItem); + spriteBounds.x += itemSlot->getX(); + spriteBounds.y += itemSlot->getY(); + // if the cursor is hovering over an item append the hover context. auto context = _mouseOverItem == invItem ? InventorySpriteContext::SOLDIER_INV_SLOT.with(InventorySpriteContext::CURSOR_HOVER) : InventorySpriteContext::SOLDIER_INV_SLOT; - InventoryItemSprite(*invItem, save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); + InventoryItemSprite(*invItem, *save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); } else // hand slot { - const auto handSlotBounds = SDL_Rect{ + SDL_Rect spriteBounds = InventoryItemSprite::getHandCenteredSpriteBounds(*invItem); + spriteBounds.x += itemSlot->getX(); + spriteBounds.y += itemSlot->getY(); + + // the bounds for the hand overlay are inset by one pixel compared to the standard hand box, + // because the outline is drawn inside the hand box. + const auto handOverlayBounds = SDL_Rect{ static_cast(itemSlot->getX() + 1), static_cast(itemSlot->getY() + 1), (RuleInventory::HAND_SLOT_W) - 1, @@ -330,8 +337,8 @@ void Inventory::drawItems() }; auto context = _mouseOverItem == invItem ? InventorySpriteContext::SOLDIER_INV_HAND.with(InventorySpriteContext::CURSOR_HOVER) : InventorySpriteContext::SOLDIER_INV_HAND; - InventoryItemSprite(*invItem, save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); - InventoryItemSprite(*invItem, save, *_items, handSlotBounds).drawHandOverlay(context, _animFrame); + InventoryItemSprite(*invItem, *save, *_items, spriteBounds).draw(*_bigObs, context, _animFrame); + InventoryItemSprite(*invItem, *save, *_items, handOverlayBounds).drawHandOverlay(context, _animFrame); } } @@ -365,13 +372,13 @@ void Inventory::drawItems() } const auto itemSlot = groundItem->getSlot(); - SDL_Rect spriteBounds = groundItem->getInvSpriteBounds(); + SDL_Rect spriteBounds = InventoryItemSprite::getGroundSlotSpriteBounds(*groundItem, _groundOffset); spriteBounds.x += itemSlot->getX(); spriteBounds.y += itemSlot->getY(); auto context = _mouseOverItem == groundItem ? InventorySpriteContext::SOLDIER_INV_SLOT.with(InventorySpriteContext::CURSOR_HOVER) : InventorySpriteContext::SOLDIER_INV_SLOT; - InventoryItemSprite(*groundItem, save, *_items, spriteBounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_GROUND, _animFrame); + InventoryItemSprite(*groundItem, *save, *_items, spriteBounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_GROUND, _animFrame); // item stacking if (_stackLevel[groundItem->getSlotX()][groundItem->getSlotY()] > 1) @@ -400,9 +407,9 @@ void Inventory::drawSelectedItem() if (_selItem) { _selection->clear(); - SDL_Rect bounds = _selItem->getHandCenteredSpriteBounds(); + SDL_Rect bounds = InventoryItemSprite::getHandCenteredSpriteBounds(*_selItem); const auto save = _game->getSavedGame()->getSavedBattle(); - InventoryItemSprite(*_selItem, save, *_selection, bounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); + InventoryItemSprite(*_selItem, *save, *_selection, bounds).draw(*_bigObs, InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); auto handSlotBounds = SDL_Rect{ static_cast(_selection->getX()), @@ -411,7 +418,7 @@ void Inventory::drawSelectedItem() RuleInventory::HAND_SLOT_H, }; // this renders no effects by default, but allows for scripting. - InventoryItemSprite(*_selItem, save, *_selection, bounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); + InventoryItemSprite(*_selItem, *save, *_selection, bounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_CURSOR, _animFrame); } } diff --git a/src/Battlescape/InventoryItemSprite.cpp b/src/Battlescape/InventoryItemSprite.cpp index 698561cc31..6026134b91 100644 --- a/src/Battlescape/InventoryItemSprite.cpp +++ b/src/Battlescape/InventoryItemSprite.cpp @@ -44,7 +44,7 @@ namespace OpenXcom */ void InventoryItemSprite::draw(const SurfaceSet& surfaceSet, InventorySpriteContext context, int animationFrame) { - const Surface* sprite = _battleItem->getBigSprite(&surfaceSet, _save, animationFrame); + const Surface* sprite = getBigSprite(&surfaceSet, _save, animationFrame); if (!sprite) { @@ -75,6 +75,101 @@ void InventoryItemSprite::drawHandOverlay(InventorySpriteContext context, int an if (context.options & InventorySpriteContext::DRAW_TWOHAND) { drawTwoHandIndicator(); } } +std::tuple getDimensions(const RuleItem& ruleItem) +{ + // item dimensions in inventory units. + int invSlotW = ruleItem.getInventoryWidth(); + int invSlotH = ruleItem.getInventoryHeight(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = invSlotW * RuleInventory::SLOT_W; + Uint16 itemH = invSlotH * RuleInventory::SLOT_H; + + return std::tuple{ invSlotW ,invSlotH, itemW, itemH }; +} + +/** + * @brief Gets the bounds for proper display of an inventory sprite relative to it's inventory slot. + * @param groundOffset the number of inventory units the ground inventory slot is offset. + * @return A SDL_Rect describing the sprite's bounds, relative to it's inventory slot. +*/ +SDL_Rect InventoryItemSprite::getInvSpriteBounds(const BattleItem& battleItem) +{ + assert(battleItem.getSlot()->getType() == INV_SLOT && "getInvSpriteBounds called on non-inventory item."); + const auto itemRules = battleItem.getRules(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = itemRules->getInventoryWidth() * RuleInventory::SLOT_W; + Uint16 itemH = itemRules->getInventoryHeight() * RuleInventory::SLOT_H; + + // offset the box by its place in inventory container * slot size. + Sint16 itemX = battleItem.getSlotX() * RuleInventory::SLOT_W; + Sint16 itemY = battleItem.getSlotY() * RuleInventory::SLOT_H; + + return SDL_Rect{ itemX, itemY, itemW, itemH }; +} + +SDL_Rect InventoryItemSprite::getGroundSlotSpriteBounds(const BattleItem& battleItem, int groundOffset) +{ + assert(battleItem.getSlot()->getType() == INV_GROUND && "getGroundSlotSpriteBounds called on non-ground item."); + const auto itemRules = battleItem.getRules(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = itemRules->getInventoryWidth() * RuleInventory::SLOT_W; + Uint16 itemH = itemRules->getInventoryHeight() * RuleInventory::SLOT_H; + + // offset the box by place in the ground container, after taking into account the ground offset (in inventory units) + Sint16 itemX = (battleItem.getSlotX() - groundOffset) * RuleInventory::SLOT_W; + Sint16 itemY = battleItem.getSlotY() * RuleInventory::SLOT_H; + + return SDL_Rect{ itemX, itemY, itemW, itemH }; +} + +SDL_Rect InventoryItemSprite::getHandCenteredSpriteBounds(const RuleItem& ruleItem) +{ + // no assert, calling this on items not in hand is potentially valid. + + // item dimensions in inventory units. + int invSlotW = ruleItem.getInventoryWidth(); + int invSlotH = ruleItem.getInventoryHeight(); + + // sprite bounding dimensions in pixels. + Uint16 itemW = invSlotW * RuleInventory::SLOT_W; + Uint16 itemH = invSlotH * RuleInventory::SLOT_H; + + // position item by half the difference in item size and hand slot size in order to center. + Sint16 itemX = (RuleInventory::HAND_W - invSlotW) * RuleInventory::SLOT_W / 2; + Sint16 itemY = (RuleInventory::HAND_H - invSlotH) * RuleInventory::SLOT_H / 2; + + return SDL_Rect{ itemX, itemY, itemW, itemH }; +} + +/** + * Gets the item's inventory sprite. + * @return Return current inventory sprite. + */ +const Surface* InventoryItemSprite::getBigSprite(const SurfaceSet* set, const SavedBattleGame* save, int animFrame) const +{ + int spriteIndex = _ruleItem.getBigSprite(); + if (spriteIndex == -1) { return nullptr; } + + const Surface* ruleSurf = set->getFrame(spriteIndex); + //enforce compatibility with basic version + if (ruleSurf == nullptr) + { + throw Exception("Image missing in 'BIGOBS.PCK' for item '" + _ruleItem.getType() + "'"); + } + + spriteIndex = ModScript::scriptFunc2( + &_ruleItem, + spriteIndex, 0, + _battleItem, save, BODYPART_ITEM_INVENTORY, animFrame, 0 + ); + + auto* scriptSurf = set->getFrame(spriteIndex); + return scriptSurf != nullptr ? scriptSurf : ruleSurf; +} + namespace // some short helper functions. { /** @@ -106,23 +201,6 @@ std::pair bottomRight(const SDL_Rect& bounds, int numW, int numH, int return std::pair{bounds.x + bounds.w - numW - spacing, bounds.y + bounds.h - numH - spacing}; } -/** - * @brief Gets an element member allowing for the fact that the element or the interface might not exist and return null. - * @param member pointer to the member of the element we want. - * @param fallback value to return if the element is not found. - * @return The member of the element, or fallback if the element was not found. -*/ -int getInterfaceElementMember(const Mod& mod, const std::string& interfaceName, const std::string& elementName, int Element::* member, int fallback = 0) -{ - const auto interface = mod.getInterface(interfaceName); - if (interface == nullptr) { return fallback; } - - auto element = interface->getElement(elementName); - if (element == nullptr) { return fallback; } - - return element->*member; -} - } // namespace /** @@ -160,7 +238,7 @@ void InventoryItemSprite::drawGrenadePrimedIndicator(int animationFrame) const if (_battleItem->getFuseTimer() < 0) { return; } /// TODO: This should be const, but the get methods are not. - const Surface* primedIndicator = const_cast(_save->getMod())->getSurfaceSet("SCANG.DAT")->getFrame(6); + const Surface* primedIndicator = const_cast(_mod).getSurfaceSet("SCANG.DAT")->getFrame(6); // if the grenade is primed, but without the fuse enabled, it gets a grey indicator. This is used for flares in XCF. int newColor = _battleItem->isFuseEnabled() ? 0 : 32; /// TODO: these colors should be moved to the interface. @@ -169,19 +247,19 @@ void InventoryItemSprite::drawGrenadePrimedIndicator(int animationFrame) const namespace { -Surface* getCorpseStateIndicator(const SavedBattleGame& save, const BattleUnit& unit) { +Surface* getCorpseStateIndicator(const SavedBattleGame& save, const Mod& mod, const BattleUnit& unit) { /// TODO: This should be const, but the get methods are not. - Mod& mod = const_cast(*save.getMod()); + Mod& mutableMod = const_cast(*save.getMod()); const auto enviro = save.getEnviroEffects(); - if (unit.getFire() > 0) { return mod.getSurface("BigBurnIndicator", false); } - if (unit.getFatalWounds() > 0) { return mod.getSurface("BigWoundIndicator", false); } + if (unit.getFire() > 0) { return mutableMod.getSurface("BigBurnIndicator", false); } + if (unit.getFatalWounds() > 0) { return mutableMod.getSurface("BigWoundIndicator", false); } if (unit.hasNegativeHealthRegen()) { - return enviro && !enviro->getInventoryShockIndicator().empty() ? mod.getSurface(enviro->getInventoryShockIndicator(), false) - : mod.getSurface("BigShockIndicator", false); + return enviro && !enviro->getInventoryShockIndicator().empty() ? mutableMod.getSurface(enviro->getInventoryShockIndicator(), false) + : mutableMod.getSurface("BigShockIndicator", false); } - return mod.getSurface("BigStunIndicator", false); + return mutableMod.getSurface("BigStunIndicator", false); } } @@ -191,7 +269,7 @@ void InventoryItemSprite::drawCorpseIndicator(int animationFrame) const const auto unit = _battleItem->getUnit(); if (!unit || unit->getStatus() != STATUS_UNCONSCIOUS) { return; } - if (const Surface* corpseStateIndicator = getCorpseStateIndicator(*_save, *unit)) + if (const Surface* corpseStateIndicator = getCorpseStateIndicator(*_save, _mod, *unit)) { corpseStateIndicator->blitNShade(&_target, _bounds.x, _bounds.y, triangleWave(animationFrame, 8, 4)); } @@ -205,15 +283,15 @@ void InventoryItemSprite::drawFatalWoundIndicator() int woundCount = unit->getFatalWounds(); /// TODO: this element should be replaced with a more descriptively named element. - int woundColor = getInterfaceElementMember(*_save->getMod(), "inventory", "numStack", &Element::color2); + int woundColor = getInterfaceElementMember(_mod, "inventory", "numStack", &Element::color2).value_or(0); drawNumberCorner(bottomRight, 0, woundCount, woundColor, 0, true); } void InventoryItemSprite::drawAmmoIndicator() { - int ammoColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numAmmoRight", &Element::color) : - _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numAmmoLeft", &Element::color) + int ammoColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(_mod, "battlescape", "numAmmoRight", &Element::color).value_or(0) : + _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(_mod, "battlescape", "numAmmoLeft", &Element::color).value_or(0) : throw std::logic_error("item in hand with bad hand value."); for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) @@ -232,8 +310,8 @@ void InventoryItemSprite::drawMedkitIndicator() { if (_ruleItem.getBattleType() != BT_MEDIKIT) { return; } - int medkitColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numMedikitRight", &Element::color) : - _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "numMedikitLeft", &Element::color) + int medkitColor = _battleItem->getSlot()->isRightHand() ? getInterfaceElementMember(_mod, "battlescape", "numMedikitRight", &Element::color).value_or(0) : + _battleItem->getSlot()->isLeftHand() ? getInterfaceElementMember(_mod, "battlescape", "numMedikitLeft", &Element::color).value_or(0) : throw std::logic_error("item in hand with bad hand value."); drawNumberCorner(bottomLeft, 1, _battleItem->getPainKillerQuantity(), medkitColor, -2); @@ -245,8 +323,8 @@ void InventoryItemSprite::drawTwoHandIndicator() { if (!_ruleItem.isTwoHanded()) { return; } - int color = _ruleItem.isBlockingBothHands() ? getInterfaceElementMember(*_save->getMod(), "battlescape", "twoHandedRed", &Element::color) - : getInterfaceElementMember(*_save->getMod(), "battlescape", "twoHandedGreen", &Element::color); + int color = _ruleItem.isBlockingBothHands() ? getInterfaceElementMember(_mod, "battlescape", "twoHandedRed", &Element::color).value_or(0) + : getInterfaceElementMember(_mod, "battlescape", "twoHandedGreen", &Element::color).value_or(0); drawNumberCorner(bottomRight, 1, 2, color); } diff --git a/src/Battlescape/InventoryItemSprite.h b/src/Battlescape/InventoryItemSprite.h index 938c1237ff..85fa46ce24 100644 --- a/src/Battlescape/InventoryItemSprite.h +++ b/src/Battlescape/InventoryItemSprite.h @@ -20,6 +20,7 @@ #include "../Engine/Script.h" #include "../Interface/NumberText.h" #include "../Savegame/BattleItem.h" +#include "../Savegame/SavedBattleGame.h" namespace OpenXcom { @@ -28,6 +29,7 @@ class Surface; class SurfaceSet; class SavedBattleGame; class BattleItem; +class Mod; class RuleItem; class ScriptParserBase; @@ -65,10 +67,11 @@ struct InventorySpriteContext static const InventorySpriteContext SOLDIER_INV_HAND; static const InventorySpriteContext SOLDIER_INV_SLOT; static const InventorySpriteContext SOLDIER_INV_GROUND; - static const InventorySpriteContext ALIEN_INV_HAND; - static const InventorySpriteContext BATTSCAPE_HAND; static const InventorySpriteContext SOLDIER_INV_AMMO; static const InventorySpriteContext SOLDIER_INV_CURSOR; + static const InventorySpriteContext ALIEN_INV_HAND; + static const InventorySpriteContext BATTSCAPE_HAND; + static const InventorySpriteContext UFOPEDIA_ARTICLE; /// returns a copy of the object with the new value or-ed in. InventorySpriteContext with(RenderContext otherContext) const @@ -87,6 +90,7 @@ inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_CURS inline constexpr InventorySpriteContext InventorySpriteContext::SOLDIER_INV_AMMO{ RenderContext::SCREEN_INVENTORY, OverlayOptions::DRAW_NONE}; inline constexpr InventorySpriteContext InventorySpriteContext::ALIEN_INV_HAND{ RenderContext::SCREEN_ALIEN_INV, OverlayOptions::DRAW_NONE}; inline constexpr InventorySpriteContext InventorySpriteContext::BATTSCAPE_HAND{ RenderContext::SCREEN_BATTSCAPE, static_cast(DRAW_GRENADE | DRAW_AMMO | DRAW_MEDIKIT | DRAW_TWOHAND)}; +inline constexpr InventorySpriteContext InventorySpriteContext::UFOPEDIA_ARTICLE{ RenderContext::SCREEN_UFOPEDIA, OverlayOptions::DRAW_NONE }; /// Namespace for segregating InventorySpriteContext scripting functions. namespace InventorySpriteContextScript @@ -110,9 +114,14 @@ class InventoryItemSprite private: /// Worker for pixel level script blitting. ScriptWorkerBlit _scriptWorker{}; - const BattleItem* _battleItem; + /// BattleItem coresponding with this sprite. Null if initialized in a ufopedia context. + const BattleItem* _battleItem = nullptr; + /// RuleItem coresponding with this sprite. Always initialized. const RuleItem& _ruleItem; - const SavedBattleGame* _save; + /// SavedBattleGame context the sprite is operating in. Null if initialized in a ufopedia context. + const SavedBattleGame* _save = nullptr; + /// Mod context the sprite is operating in. Null if initalized in a ufopedia context. + const Mod& _mod; /// Surface this item should be draw to. Surface& _target; /// Bounding box for the sprite. @@ -124,26 +133,40 @@ class InventoryItemSprite /** * @brief Creates a new inventory item sprite. * @param target The surface the item is going to be rendered to. - * @param spriteBounds The bounds of the sprite. Not necessarily equal to target's area. + * @param spriteBounds The bounds of the sprite relative to target. */ - InventoryItemSprite(const BattleItem& battleItem, const SavedBattleGame* save, Surface& target, const SDL_Rect& spriteBounds) - : _battleItem(&battleItem), _ruleItem(*battleItem.getRules()), _save(save), _target(target), _bounds(spriteBounds) {} + InventoryItemSprite(const BattleItem& battleItem, const SavedBattleGame& save, Surface& target, const SDL_Rect& spriteBounds) + : _battleItem(&battleItem), _ruleItem(*_battleItem->getRules()), _save(&save), _mod(*_save->getMod()), _target(target), _bounds(spriteBounds) {} /** - * @brief Creates a new inventory item sprite. Bounds are assumed to be equal to target's dimensions with x and y of 0, 0. + * @brief Create a new invetory item sprite, without requiring a battle context (for the ufopedia). * @param target The surface the item is going to be rendered to. + * @param spriteBounds The bounds of the sprite, relative to target. */ - InventoryItemSprite(const BattleItem& battleItem, const SavedBattleGame* save, Surface& target) - : _battleItem(&battleItem), _ruleItem(*battleItem.getRules()), _save(save), _target(target), - _bounds(SDL_Rect{ 0, 0, static_cast(_target.getWidth()), static_cast(target.getHeight()) }) {} - + InventoryItemSprite(const RuleItem& ruleItem, const Mod& mod, Surface& target, const SDL_Rect& spriteBounds) + : _ruleItem(ruleItem), _mod(mod), _target(target), _bounds(spriteBounds) {} /// Draw the sprite, including scripted recolor and blitting. void draw(const SurfaceSet& surfaceSet, InventorySpriteContext context, int animationFrame = 0); /// Draw the hand overlay, which does not include the sprite. void drawHandOverlay(InventorySpriteContext context, int animationFrame = 0); + /// Gets the bounds for proper display of an inventory sprite relative to it's inventory slot. + static SDL_Rect getInvSpriteBounds(const BattleItem& battleItem); + /// Gets the bounds for display of an inventory sprite relative to a hand slot (fixed size). + static SDL_Rect getHandCenteredSpriteBounds(const BattleItem& battleItem) + { + return InventoryItemSprite::getHandCenteredSpriteBounds(*battleItem.getRules()); + } + /// Gets the bounds for display of an inventory sprite relative to a hand slot (fixed size). + static SDL_Rect getHandCenteredSpriteBounds(const RuleItem& ruleItem); + /// Gets the bounds for display of an inventory sprite relative to a ground inventory slot. + static SDL_Rect getGroundSlotSpriteBounds(const BattleItem& battleItem, int groundOffset); + private: + /// gets the appropriate big item sprite, including scripted replacement. + const Surface* getBigSprite(const SurfaceSet* set, const SavedBattleGame* save, int animFrame) const; + void drawGrenadePrimedIndicator(int animationFrame) const; void drawAmmoIndicator(); void drawMedkitIndicator(); diff --git a/src/Battlescape/InventoryState.cpp b/src/Battlescape/InventoryState.cpp index ec71777333..921fa60ae6 100644 --- a/src/Battlescape/InventoryState.cpp +++ b/src/Battlescape/InventoryState.cpp @@ -530,8 +530,9 @@ void InventoryState::init() armorSurface->blitNShade(_soldier, 0, 0); } } - auto bounds = SDL_Rect{ 0, 0, static_cast(_soldier->getWidth()), static_cast(_soldier->getHeight()) }; - SpriteOverlay(*_soldier, bounds, _game->getSavedGame()->getSavedBattle()).draw(*unit->getArmor(), unit, s); + auto bounds = SpriteOverlay::getSurfaceBounds(*_soldier); + auto save = _game->getSavedGame()->getSavedBattle(); + SpriteOverlay(*_soldier, bounds, save).draw(*unit->getArmor(), unit, _inv->getAnimFrame()); // coming from InventoryLoad window... if (_globalLayoutIndex > -1) @@ -2042,9 +2043,9 @@ void InventoryState::handle(Action *action) */ void InventoryState::think() { + int anim = _inv->getAnimFrame(); if (_mouseHoverItem) { - int anim = _inv->getAnimFrame(); int seq = std::max(((anim - _mouseHoverItemFrame) / 10) - 1, 0); // `-1` cause that first item will be show bit more longer int modulo = 0; for (int slot = 0; slot < RuleItem::AmmoSlotMax; ++slot) @@ -2093,10 +2094,10 @@ void InventoryState::think() r.h -= 2; _selAmmo->drawRect(&r, Palette::blockOffset(0)+15); - const SDL_Rect spriteBounds = firstAmmo->getHandCenteredSpriteBounds(); + const SDL_Rect spriteBounds = InventoryItemSprite::getHandCenteredSpriteBounds(*firstAmmo); const auto save = _game->getSavedGame()->getSavedBattle(); - const auto surfaceSet = *_game->getMod()->getSurfaceSet("BIGOBS.PCK", anim); - InventoryItemSprite(*firstAmmo, save, *_selAmmo, spriteBounds).draw(surfaceSet, InventorySpriteContext::SOLDIER_INV_AMMO, anim); + const auto& surfaceSet = *_game->getMod()->getSurfaceSet("BIGOBS.PCK", anim); + InventoryItemSprite(*firstAmmo, *save, *_selAmmo, spriteBounds).draw(surfaceSet, InventorySpriteContext::SOLDIER_INV_AMMO, anim); constexpr auto handSlotBounds = SDL_Rect{ static_cast(1), @@ -2104,13 +2105,20 @@ void InventoryState::think() RuleInventory::HAND_SLOT_W-1, RuleInventory::HAND_SLOT_H-2, }; - InventoryItemSprite(*firstAmmo, save, *_selAmmo, handSlotBounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_AMMO, anim); + InventoryItemSprite(*firstAmmo, *save, *_selAmmo, handSlotBounds).drawHandOverlay(InventorySpriteContext::SOLDIER_INV_AMMO, anim); } else { _selAmmo->clear(); } } + /// animate the paperdoll scripts. + if (BattleUnit* unit = _battleGame->getSelectedUnit()) + { + auto bounds = SpriteOverlay::getSurfaceBounds(*_soldier); + auto save = _game->getSavedGame()->getSavedBattle(); + SpriteOverlay(*_soldier, bounds, save).draw(*unit->getArmor(), unit, anim); + } State::think(); } diff --git a/src/Battlescape/SpriteOverlay.cpp b/src/Battlescape/SpriteOverlay.cpp index 9957790680..1d5663976c 100644 --- a/src/Battlescape/SpriteOverlay.cpp +++ b/src/Battlescape/SpriteOverlay.cpp @@ -29,10 +29,23 @@ void SpriteOverlay::draw(const Rule& rule, const Battle* battle, Context* contex ModScript::scriptCallback(&rule, battle, _save, this, context, animationFrame); } +/** + * @brief Draws a scripted sprite overlay for hooks lacking a context. + * @tparam Battle The battlescape instance of the object associated with this script. + * @tparam Callback The ModScript struct to use when calling this script + * @tparam Rule The rule object associated with the object this script targets. + * @param animationFrame The current animation frame. Defaults to 0. +*/ +template +void SpriteOverlay::draw(const Rule& rule, const Battle* battle, int animationFrame) +{ + ModScript::scriptCallback(&rule, battle, _save, this, animationFrame); +} + /// Draw the paperdoll overlay. -template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, Soldier* soldier, int animationFrame); +template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, int animationFrame); /// Draw the unitRank overlay. -template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, Soldier* soldier, int animationFrame); +template void SpriteOverlay::draw(const Armor& armor, const BattleUnit* unit, int animationFrame); /** * @brief Draws an overlay for an inventory sprite/bigobj. @@ -48,96 +61,91 @@ void SpriteOverlay::draw(const RuleItem& ruleItem, const BattleItem* battleItem, } }; -//// Script binding -namespace SpriteOverlayScript { - /// Draws a number on the surface the sprite targets. -void drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color) +void SpriteOverlay::drawNumber(int value, int x, int y, int width, int height, int color) { - dest->_numberRender.clear(); - dest->_numberRender.setX(dest->_bounds.x + x); - dest->_numberRender.setY(dest->_bounds.y + y); + _numberRender.clear(); + _numberRender.setX(_bounds.x + x); + _numberRender.setY(_bounds.y + y); // avoid resizing if possible. - if (width > dest->_numberRender.getWidth()) { dest->_numberRender.setWidth(width); } - if (height != dest->_numberRender.getHeight()) { dest->_numberRender.setWidth(height); } + if (width > _numberRender.getWidth()) { _numberRender.setWidth(width); } + if (height != _numberRender.getHeight()) { _numberRender.setWidth(height); } - dest->_numberRender.setPalette(dest->_target.getPalette()); - dest->_numberRender.setColor(static_cast(color)); - dest->_numberRender.setBordered(false); - dest->_numberRender.setValue(value); - dest->_numberRender.blit(dest->_target.getSurface()); + _numberRender.setPalette(_target.getPalette()); + _numberRender.setColor(static_cast(color)); + _numberRender.setBordered(false); + _numberRender.setValue(value); + _numberRender.blit(_target.getSurface()); } /// Draws text on on the surface the sprite targets. -void drawText(SpriteOverlay* dest, const std::string& text, int x, int y, int width, int height, int color) +void SpriteOverlay::drawText(const std::string& text, int x, int y, int width, int height, int color) { - auto surfaceText = Text(width, height, dest->_bounds.x + x, dest->_bounds.y + y); - surfaceText.setPalette(dest->_target.getPalette()); + auto surfaceText = Text(width, height, _bounds.x + x, _bounds.y + y); + surfaceText.setPalette(_target.getPalette()); auto temp = Language(); // note getting the selected language is tough here, and might not even be what is wanted. - const auto mod = dest->_save->getMod(); + const auto mod = _save->getMod(); surfaceText.initText(mod->getFont("FONT_BIG"), mod->getFont("FONT_SMALL"), &temp); surfaceText.setSmall(); surfaceText.setColor(color); surfaceText.setText(text); - surfaceText.blit(dest->_target.getSurface()); + surfaceText.blit(_target.getSurface()); } -void blit(SpriteOverlay* dest, const Surface* source, int x, int y) +void SpriteOverlay::blit(const Surface* source, int x, int y) { - source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, 0, false, 0); + source->blitNShade(&_target, _bounds.x + x, _bounds.y + y, 0, false, 0); } -void blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2) +void SpriteOverlay::blitCrop(const Surface* source, int x1, int y1, int x2, int y2) { - const auto crop = GraphSubset({dest->_bounds.x + x1, dest->_bounds.x + x2}, {dest->_bounds.y + y1, dest->_bounds.y + y2}); - source->blitNShade(&dest->_target, dest->_bounds.x, dest->_bounds.y, 0, crop); + const auto crop = GraphSubset({_bounds.x + x1, _bounds.x + x2}, {_bounds.y + y1, _bounds.y + y2}); + source->blitNShade(&_target, _bounds.x, _bounds.y, 0, crop); } -void blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2) +void SpriteOverlay::blitShadeCrop(const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2) { const auto crop = GraphSubset({x1, x2}, {y1, y2}); - source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, shade, crop); + source->blitNShade(&_target, _bounds.x + x, _bounds.y + y, shade, crop); } -void blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade) +void SpriteOverlay::blitShade(const Surface* source, int x, int y, int shade) { - source->blitNShade(&dest->_target, dest->_bounds.x + x, dest->_bounds.y + y, shade, false, 0); + source->blitNShade(&_target, _bounds.x + x, _bounds.y + y, shade, false, 0); } -void blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor) +void SpriteOverlay::blitShadeRecolor(const Surface* source, int x, int y, int shade, int newColor) { - source->blitNShade(&dest->_target, dest->_bounds.x, dest->_bounds.y, shade, false, newColor); + source->blitNShade(&_target, _bounds.x, _bounds.y, shade, false, newColor); } -void drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color) +void SpriteOverlay::drawLine(int x1, int y1, int x2, int y2, int color) { - dest->_target.drawLine(dest->_bounds.x + x1, dest->_bounds.y + y1, dest->_bounds.x + x2, dest->_bounds.y + y2, color); + _target.drawLine(_bounds.x + x1, _bounds.y + y1, _bounds.x + x2, _bounds.y + y2, color); } -void drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color) +void SpriteOverlay::drawRect(int x1, int y1, int x2, int y2, int color) { - dest->_target.drawRect(dest->_bounds.x + x1, dest->_bounds.y + y1, dest->_bounds.x + x2, dest->_bounds.y + y2, color); + _target.drawRect(_bounds.x + x1, _bounds.y + y1, _bounds.x + x2, _bounds.y + y2, color); } -void drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color) +void SpriteOverlay::drawCirc(int x, int y, int radius, int color) { - dest->_target.drawCircle(dest->_bounds.x + x, dest->_bounds.y + y, radius, color); + _target.drawCircle(_bounds.x + x, _bounds.y + y, radius, color); } +//// Script binding +namespace SpriteOverlayScript { + std::string debugDisplayScript(const SpriteOverlay* overlay) { - if (overlay) - { - std::ostringstream output; - output << " (x:" << overlay->_bounds.x << " y:" << overlay->_bounds.y << " w:" << overlay->_bounds.w << " h:" << overlay->_bounds.h << ")"; - return output.str(); - } - else - { - return "null"; - } + if (overlay == nullptr) { return "null"; } + + std::ostringstream output; + output << " (x:" << overlay->_bounds.x << " y:" << overlay->_bounds.y << " w:" << overlay->_bounds.w << " h:" << overlay->_bounds.h << ")"; + return output.str(); } } // SpriteOverlayScript @@ -146,17 +154,17 @@ void SpriteOverlay::ScriptRegister(ScriptParserBase* parser) { Bind spriteOverlayBinder = { parser }; - spriteOverlayBinder.add<&SpriteOverlayScript::blit>("blit", "Blits a sprite onto the overlay. (sprite x y)"); - spriteOverlayBinder.add<&SpriteOverlayScript::blitCrop>("blitCrop", "Blits a sprite onto the overlay with a crop. (sprite x y cropX1 cropY1 cropX2 cropY2)"); - spriteOverlayBinder.add<&SpriteOverlayScript::blitShade>("blitShade", "Blits and shades a sprite onto the overlay. (sprite x y shade)"); - spriteOverlayBinder.add<&SpriteOverlayScript::blitShadeRecolor>("blitShadeRecolor", "Blits, shades, and recolors a sprite onto the overlay. (sprite x y shade color)"); + spriteOverlayBinder.add<&SpriteOverlay::blit>("blit", "Blits a sprite onto the overlay. (sprite x y)"); + spriteOverlayBinder.add<&SpriteOverlay::blitCrop>("blitCrop", "Blits a sprite onto the overlay with a crop. (sprite x y cropX1 cropY1 cropX2 cropY2)"); + spriteOverlayBinder.add<&SpriteOverlay::blitShade>("blitShade", "Blits and shades a sprite onto the overlay. (sprite x y shade)"); + spriteOverlayBinder.add<&SpriteOverlay::blitShadeRecolor>("blitShadeRecolor", "Blits, shades, and recolors a sprite onto the overlay. (sprite x y shade color)"); - spriteOverlayBinder.add<&SpriteOverlayScript::drawNumber>("drawNumber", "Draws number on the overlay. (number x y width height color)"); - spriteOverlayBinder.add<&SpriteOverlayScript::drawText>("drawText", "Draws text on the overlay. (text x y width height color"); + spriteOverlayBinder.add<&SpriteOverlay::drawNumber>("drawNumber", "Draws number on the overlay. (number x y width height color)"); + spriteOverlayBinder.add<&SpriteOverlay::drawText>("drawText", "Draws text on the overlay. (text x y width height color"); - spriteOverlayBinder.add<&SpriteOverlayScript::drawLine>("drawLine", "Draws a line on the overlay. (x1 y1 x2 y2 color)"); - spriteOverlayBinder.add<&SpriteOverlayScript::drawRect>("drawRect", "Draws a rectangle on the overlay. (x1 y1 x2 y2 color)"); - spriteOverlayBinder.add<&SpriteOverlayScript::drawCirc>("drawCirc", "Draws a circle on the overlay. (x y radius color)"); + spriteOverlayBinder.add<&SpriteOverlay::drawLine>("drawLine", "Draws a line on the overlay. (x1 y1 x2 y2 color)"); + spriteOverlayBinder.add<&SpriteOverlay::drawRect>("drawRect", "Draws a rectangle on the overlay. (x1 y1 x2 y2 color)"); + spriteOverlayBinder.add<&SpriteOverlay::drawCirc>("drawCirc", "Draws a circle on the overlay. (x y radius color)"); spriteOverlayBinder.add<&SpriteOverlay::getWidth>("getWidth", "Gets the width of this overlay."); spriteOverlayBinder.add<&SpriteOverlay::getHeight>("getHeight", "Gets the height of this overlay."); @@ -192,7 +200,7 @@ ModScript::HandOverlayParser::HandOverlayParser(ScriptGlobal* shared, const std: * Constructor of hand overlay script parser. */ ModScript::UnitPaperdollOverlayParser::UnitPaperdollOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) - : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "soldier", "anim_frame" } + : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "anim_frame" } { BindBase bindBase{ this }; bindBase.addCustomPtr("rules", mod); @@ -202,7 +210,7 @@ ModScript::UnitPaperdollOverlayParser::UnitPaperdollOverlayParser(ScriptGlobal* * Constructor of hand overlay script parser. */ ModScript::UnitRankOverlayParser::UnitRankOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod) - : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "soldier", "anim_frame" } + : ScriptParserEvents{ shared, name, "unit", "battle_game", "overlay", "anim_frame" } { BindBase bindBase{ this }; bindBase.addCustomPtr("rules", mod); diff --git a/src/Battlescape/SpriteOverlay.h b/src/Battlescape/SpriteOverlay.h index cfbfc4d7bd..8a37e26499 100644 --- a/src/Battlescape/SpriteOverlay.h +++ b/src/Battlescape/SpriteOverlay.h @@ -31,19 +31,6 @@ class SavedBattleGame; /// Namespace for segregating SpriteOverlay scripting functions. namespace SpriteOverlayScript { -void drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color); -void drawText(SpriteOverlay* dest, const std::string& text, int width, int height, int x, int y, int color); - -void blit(SpriteOverlay* dest, const Surface* source, int x, int y); -void blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2); -void blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2); -void blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade); -void blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor); - -void drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); -void drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); -void drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color); - std::string debugDisplayScript(const SpriteOverlay* overlay); } @@ -70,12 +57,6 @@ class SpriteOverlay */ SpriteOverlay(Surface& target, const SDL_Rect& spriteBounds, const SavedBattleGame *save) : _target(target), _bounds(spriteBounds), _save(save) {} - /** - * @brief Creates a new InventoryItemSprite should not be stored or persist beyond the local scope. - * @param target The surface the sprite is rendered to. The bounds are infered to be the size of this surface, with a x,y of 0, 0. - */ - SpriteOverlay(Surface& target, const SavedBattleGame* save) - : _target(target), _bounds(SDL_Rect{ 0, 0, static_cast(_target.getWidth()), static_cast(_target.getHeight()) }), _save(save) { } // no copy or move. SpriteOverlay(SpriteOverlay&) = delete; @@ -85,6 +66,10 @@ class SpriteOverlay template void draw(const Rule& rule, const Battle* battle, Context* context, int animationFrame = 0); + /// Draw the scripted overlay for hooks lacking a context object. + template + void draw(const Rule& rule, const Battle* battle, int animationFrame = 0); + void draw(const RuleItem& ruleItem, const BattleItem* battleItem, InventorySpriteContext* context, int animationFrame); // these methods are exposed for scripting. @@ -94,24 +79,31 @@ class SpriteOverlay /// Gets the height of this overlay. [[nodiscard]] int getHeight() const { return _bounds.h; } - friend void SpriteOverlayScript::drawNumber(SpriteOverlay* dest, int value, int x, int y, int width, int height, int color); - friend void SpriteOverlayScript::drawText(SpriteOverlay* dest, const std::string& text, int x, int y, int width, int height, int color); - - friend void SpriteOverlayScript::blit(SpriteOverlay* dest, const Surface* source, int x, int y); - friend void SpriteOverlayScript::blitCrop(SpriteOverlay* dest, const Surface* source, int x1, int y1, int x2, int y2); - friend void SpriteOverlayScript::blitShadeCrop(SpriteOverlay* dest, const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2); - friend void SpriteOverlayScript::blitShade(SpriteOverlay* dest, const Surface* source, int x, int y, int shade); - friend void SpriteOverlayScript::blitShadeRecolor(SpriteOverlay* dest, const Surface* source, int x, int y, int shade, int newColor); - friend void SpriteOverlayScript::drawLine(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); - friend void SpriteOverlayScript::drawRect(SpriteOverlay* dest, int x1, int y1, int x2, int y2, int color); - friend void SpriteOverlayScript::drawCirc(SpriteOverlay* dest, int x, int y, int radius, int color); - friend std::string SpriteOverlayScript::debugDisplayScript(const SpriteOverlay* overlay); + /// Gets a bounding box based on a surface's size, with the x and y set to 0. + static SDL_Rect getSurfaceBounds(const Surface& surface) + { + return SDL_Rect{ 0, 0, static_cast(surface.getWidth()), static_cast(surface.getHeight()) }; + } + /// Name of class used in script. static constexpr const char* ScriptName = "SpriteOverlay"; /// Register all useful function used by script. static void ScriptRegister(ScriptParserBase* parser); + +private: + void drawNumber(int value, int x, int y, int width, int height, int color); + void drawText(const std::string& text, int x, int y, int width, int height, int color); + + void blit(const Surface* source, int x, int y); + void blitCrop(const Surface* source, int x1, int y1, int x2, int y2); + void blitShadeCrop(const Surface* source, int shade, int x, int y, int x1, int y1, int x2, int y2); + void blitShade(const Surface* source, int x, int y, int shade); + void blitShadeRecolor(const Surface* source, int x, int y, int shade, int newColor); + void drawLine(int x1, int y1, int x2, int y2, int color); + void drawRect(int x1, int y1, int x2, int y2, int color); + void drawCirc(int x, int y, int radius, int color); }; } diff --git a/src/Mod/Mod.cpp b/src/Mod/Mod.cpp index 16d272799d..1ed89de227 100644 --- a/src/Mod/Mod.cpp +++ b/src/Mod/Mod.cpp @@ -6214,6 +6214,26 @@ bool Mod::isDemigod() const return _difficultyDemigod; } +/** + * @brief Gets an element member allowing for the fact that the element or the interface might not exist. + * @tparam MemberType The type of the element member desired. + * @param member pointer to the member of the element we want. + * @param fallback value to return if the element is not found. + * @return An optional containing the member of the element if it exists. +*/ +template +std::optional getInterfaceElementMember(const Mod& mod, const std::string& interfaceName, const std::string& elementName, MemberType Element::* member) +{ + static_assert(std::is_same::value || std::is_same::value, "Element member type must be bool or int"); + + const auto interface = mod.getInterface(interfaceName, false); + if (interface == nullptr) { return std::nullopt; } + + const auto element = interface->getElement(elementName); + if (element == nullptr) { return std::nullopt; } + + return element->*member; +} //////////////////////////////////////////////////////////// // Script binding @@ -6345,21 +6365,27 @@ void getNamedSpriteScript(const Mod* mod, const Surface*& surface, const std::st * @brief Custom method for retrieving an interface element color. * @param color Out reference for the color. -1 on error. */ -void getInterfaceElementColor(const Mod* mod, int &color, const std::string& interfaceName, const std::string& elementName) +void getInterfaceElementColorScript(const Mod* mod, int &color, const std::string& interfaceName, const std::string& elementName) { - auto interface = mod->getInterface(interfaceName); - auto element = interface->getElement(elementName); - color = (interface && element) ? element->color : -1; + color = mod ? getInterfaceElementMember(*mod, interfaceName, elementName, &Element::color).value_or(-1) : -1; } /** * @brief Custom method for retrieving an interface element color2. * @param color Out reference for the color. -1 on error. */ -void getInterfaceElementColor2(const Mod* mod, int& color, const std::string& interfaceName, const std::string& elementName) +void getInterfaceElementColor2Script(const Mod* mod, int& color, const std::string& interfaceName, const std::string& elementName) { - auto interface = mod->getInterface(interfaceName); - auto element = interface->getElement(elementName); - color = (interface && element) ? element->color2 : -1; + color = mod ? getInterfaceElementMember(*mod, interfaceName, elementName, &Element::color2).value_or(-1) : -1; +} + +std::string surfaceDebugDisplayScript(const Surface* surface) +{ + if (surface == nullptr) { return "null"; } + + std::ostringstream output; + output << " (x:" << surface->getX() << " y:" << surface->getY() + << " w:" << surface->getWidth() << " h:" << surface->getHeight() << ")"; + return output.str(); } } // namespace @@ -6418,8 +6444,8 @@ void Mod::ScriptRegister(ScriptParserBase *parser) mod.add<&getSpiteScript>("getSpriteFromSet", "Gets a sprite identified by set name and index from the appropriate store. (sprite set index"); mod.add<&getNamedSpriteScript>("getNamedSprite", "Get a sprite identified by a string. (sprite spriteName)"); - mod.add<&getInterfaceElementColor>("getInterfaceElementColor", "Gets the color of a specific interface element. -1 on error. (color interface element)"); - mod.add<&getInterfaceElementColor2>("getInterfaceElementColor2", "Gets the color of a specific interface element. -1 on error. (color interface element)"); + mod.add<&getInterfaceElementColorScript>("getInterfaceElementColor", "Gets the color of a specific interface element. -1 on error. (color interface element)"); + mod.add<&getInterfaceElementColor2Script>("getInterfaceElementColor2", "Gets the color of a specific interface element. -1 on error. (color interface element)"); mod.addScriptValue<&Mod::_scriptGlobal, &ModScriptGlobal::getScriptValues>(); } diff --git a/src/Mod/Mod.h b/src/Mod/Mod.h index 87497bbf86..76e7dfd56a 100644 --- a/src/Mod/Mod.h +++ b/src/Mod/Mod.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "../Engine/Options.h" #include "../Engine/FileMap.h" @@ -88,6 +89,7 @@ class ExtraStrings; class RuleCommendations; class StatString; class RuleInterface; +struct Element; class RuleGlobe; class RuleConverter; class SoundDefinition; @@ -1107,4 +1109,8 @@ class Mod }; +/// Gets an element member allowing for the fact that the element or the interface might not exist. +template +std::optional getInterfaceElementMember(const Mod& mod, const std::string& interfaceName, const std::string& elementName, MemberType Element::* member); + } diff --git a/src/Mod/ModScript.h b/src/Mod/ModScript.h index 848afcd6f5..89849c4fb4 100644 --- a/src/Mod/ModScript.h +++ b/src/Mod/ModScript.h @@ -94,11 +94,11 @@ class ModScript { RecolorUnitParser(ScriptGlobal* shared, const std::string& name, Mod* mod); }; - struct UnitPaperdollOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, const Soldier*, int> + struct UnitPaperdollOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, int> { UnitPaperdollOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); }; - struct UnitRankOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, const Soldier*, int> + struct UnitRankOverlayParser : ScriptParserEvents, const BattleUnit*, const SavedBattleGame*, SpriteOverlay*, int> { UnitRankOverlayParser(ScriptGlobal* shared, const std::string& name, Mod* mod); }; diff --git a/src/Mod/RuleItem.cpp b/src/Mod/RuleItem.cpp index 09d2e08ed3..a7eddb25d0 100644 --- a/src/Mod/RuleItem.cpp +++ b/src/Mod/RuleItem.cpp @@ -1739,35 +1739,6 @@ int RuleItem::getSpecialChance() const { return _specialChance; } -/** - * Draws and centers the hand sprite on a surface - * according to its dimensions. - * @param texture Pointer to the surface set to get the sprite from. - * @param surface Pointer to the surface to draw to. - */ -void RuleItem::drawHandSprite(const SurfaceSet *texture, Surface *surface) const -{ - const Surface* frame = texture->getFrame(this->getBigSprite()); - frame->blitNShade(surface, this->getHandSpriteOffX(), this->getHandSpriteOffY()); -} - -/** - * item's hand spite x offset - * @return x offset - */ -int RuleItem::getHandSpriteOffX() const -{ - return (RuleInventory::HAND_W - getInventoryWidth()) * RuleInventory::SLOT_W/2; -} - -/** - * item's hand spite y offset - * @return y offset - */ -int RuleItem::getHandSpriteOffY() const -{ - return (RuleInventory::HAND_H - getInventoryHeight()) * RuleInventory::SLOT_H/2; -} /** * Gets the heal quantity of the item. diff --git a/src/Mod/RuleItem.h b/src/Mod/RuleItem.h index 3f170422e4..629c17cd4b 100644 --- a/src/Mod/RuleItem.h +++ b/src/Mod/RuleItem.h @@ -752,12 +752,6 @@ class RuleItem int getClipSize() const; /// Gets the chance of special effect like zombify or corpse explosion or mine triggering. int getSpecialChance() const; - /// Draws the item's hand sprite onto a surface. - void drawHandSprite(const SurfaceSet *texture, Surface *surface) const; - /// item's hand spite x offset - int getHandSpriteOffX() const; - /// item's hand spite y offset - int getHandSpriteOffY() const; /// Gets the medikit heal quantity. int getHealQuantity() const; /// Gets the medikit pain killer quantity. diff --git a/src/Savegame/BattleItem.cpp b/src/Savegame/BattleItem.cpp index 3e3eba6460..c610edba13 100644 --- a/src/Savegame/BattleItem.cpp +++ b/src/Savegame/BattleItem.cpp @@ -706,103 +706,6 @@ const Surface *BattleItem::getFloorSprite(const SurfaceSet *set, const SavedBatt } } -/** - * Gets the item's inventory sprite. - * @return Return current inventory sprite. - */ -const Surface *BattleItem::getBigSprite(const SurfaceSet *set, const SavedBattleGame *save, int animFrame) const -{ - int i = _rules->getBigSprite(); - if (i != -1) - { - const Surface *surf = set->getFrame(i); - //enforce compatibility with basic version - if (surf == nullptr) - { - throw Exception("Image missing in 'BIGOBS.PCK' for item '" + _rules->getType() + "'"); - } - - i = ModScript::scriptFunc2( - _rules, - i, 0, - this, save, BODYPART_ITEM_INVENTORY, animFrame, 0 - ); - - auto* newSurf = set->getFrame(i); - if (newSurf == nullptr) - { - newSurf = surf; - } - return newSurf; - } - else - { - return nullptr; - } -} -/** - * @brief Gets the items inventory sprite bounds, taking into account the items position within it's container, - * but not the position of the container (the boundary is relative to the container). - * @param groundOffset The number of inventory units the ground container is offset from 0, if any. - * @return A rectangle that describes the inventory sprite's bounds within it's container. -*/ -SDL_Rect BattleItem::getInvSpriteBounds(int groundOffset) const -{ - // item dimensions in inventory units. - int invSlotW = _rules->getInventoryWidth(); - int invSlotH = _rules->getInventoryHeight(); - - // sprite bounding dimensions in pixels. - Uint16 itemW = invSlotW * RuleInventory::SLOT_W; - Uint16 itemH = invSlotH * RuleInventory::SLOT_H; - - // determine the amount the box needs to be offset according to - Sint16 itemX, itemY; - switch (_inventorySlot->getType()) - { - case INV_SLOT: - // position item by place in inventory container * slot size. - itemX = _inventoryX * RuleInventory::SLOT_W; - itemY = _inventoryY * RuleInventory::SLOT_H; - break; - case INV_HAND: - // position item by half the difference in item size and hand slot size in order to center. - itemX = (RuleInventory::HAND_W - invSlotW) * RuleInventory::SLOT_W / 2; - itemY = (RuleInventory::HAND_H - invSlotH) * RuleInventory::SLOT_H / 2; - break; - case INV_GROUND: - // position by place in the ground container, after taking into account the ground offset (in inventory units) - itemX = (_inventoryX - groundOffset) * RuleInventory::SLOT_W; - itemY = _inventoryY * RuleInventory::SLOT_H; - break; - default: - throw std::logic_error("Item: " + _rules->getType() + " in inventory with bad enum value for slotType: " + std::to_string(_inventorySlot->getType())); - } - - return SDL_Rect{itemX, itemY, itemW, itemH}; -} - -/** - * @brief Gets the items inventory sprite bounds relative to a handslot container, assuming that it is to be centered. - * @return A rectangle that describes the inventory sprite's bounds within a hand-slot sized container. -*/ -SDL_Rect BattleItem::getHandCenteredSpriteBounds() const -{ - // item dimensions in inventory units. - int invSlotW = _rules->getInventoryWidth(); - int invSlotH = _rules->getInventoryHeight(); - - // sprite bounding dimensions in pixels. - Uint16 itemW = invSlotW * RuleInventory::SLOT_W; - Uint16 itemH = invSlotH * RuleInventory::SLOT_H; - - // position item by half the difference in item size and hand slot size in order to center. - Sint16 itemX = (RuleInventory::HAND_W - invSlotW) * RuleInventory::SLOT_W / 2; - Sint16 itemY = (RuleInventory::HAND_H - invSlotH) * RuleInventory::SLOT_H / 2; - - return SDL_Rect{itemX, itemY, itemW, itemH}; -} - /** * Check if item use any ammo. * @return True if item accept ammo. diff --git a/src/Savegame/BattleItem.h b/src/Savegame/BattleItem.h index 2c394afd86..3df7508d65 100644 --- a/src/Savegame/BattleItem.h +++ b/src/Savegame/BattleItem.h @@ -147,12 +147,6 @@ class BattleItem bool occupiesSlot(int x, int y, BattleItem *item = 0) const; /// Gets the item's floor sprite. const Surface *getFloorSprite(const SurfaceSet *set, const SavedBattleGame *save, int animFrame, int shade) const; - /// Gets the item's inventory sprite. - const Surface *getBigSprite(const SurfaceSet *set, const SavedBattleGame *save, int animFrame) const; - /// Gets the bounds of the inventory sprite. - [[nodiscard]] SDL_Rect getInvSpriteBounds(int groundOffset = 0) const; - /// Get the bounds of an item centered in a hand slot. - [[nodiscard]] SDL_Rect getHandCenteredSpriteBounds() const; /// Check if item can use any ammo. bool isWeaponWithAmmo() const; diff --git a/src/Savegame/BattleUnit.cpp b/src/Savegame/BattleUnit.cpp index d13a1971fb..5593aa068d 100644 --- a/src/Savegame/BattleUnit.cpp +++ b/src/Savegame/BattleUnit.cpp @@ -5848,7 +5848,7 @@ void setFireScript(BattleUnit *bu, int val) void getStatusScript(const BattleUnit* bu, int& status) { - status = bu ? bu->getStatus() : status; + status = bu ? bu->getStatus() : 0; } void getVisibleUnitsCountScript(BattleUnit *bu, int &ret) diff --git a/src/Savegame/SavedBattleGame.cpp b/src/Savegame/SavedBattleGame.cpp index 5ad9b6e41c..70608a3327 100644 --- a/src/Savegame/SavedBattleGame.cpp +++ b/src/Savegame/SavedBattleGame.cpp @@ -3493,16 +3493,6 @@ void getTileScript(const SavedBattleGame* sbg, const Tile*& t, int x, int y, int } } -void getEnvrionmentShockIndicator(const SavedBattleGame* sbg, const Surface*& shockIndicator) -{ - /// TODO: Should there be a default environment that sets this? - // With scripting there are better ways to handle this in any case (like a tag). - auto* enviro = sbg->getEnviroEffects(); - auto* mod = const_cast(sbg->getMod()); - shockIndicator = enviro && !enviro->getInventoryShockIndicator().empty() ? mod->getSurface(enviro->getInventoryShockIndicator(), false) - : mod->getSurface("BigShockIndicator", false); -} - void setAlienItemLevelScript(SavedBattleGame* sbg, int val) { if (sbg) @@ -3570,8 +3560,6 @@ void SavedBattleGame::ScriptRegister(ScriptParserBase* parser) sbg.add<&SavedBattleGame::getReinforcementsItemLevel>("getReinforcementsItemLevel"); sbg.add<&setReinforcementsItemLevelScript>("setReinforcementsItemLevel"); - sbg.add<&getEnvrionmentShockIndicator>("getEnvrionmentShockIndicator", "Gets the shock indicator appropriate for the environment (IE drowning for underwater), or the default if none."); - sbg.addPair("getGeoscapeGame"); sbg.add("flashMessage"); diff --git a/src/Ufopaedia/ArticleStateItem.cpp b/src/Ufopaedia/ArticleStateItem.cpp index 4d76ad68fd..742326ec1c 100644 --- a/src/Ufopaedia/ArticleStateItem.cpp +++ b/src/Ufopaedia/ArticleStateItem.cpp @@ -21,11 +21,13 @@ #include #include "Ufopaedia.h" #include "ArticleStateItem.h" +#include "../Battlescape/InventoryItemSprite.h" #include "../Mod/Mod.h" #include "../Mod/ArticleDefinition.h" #include "../Mod/RuleItem.h" #include "../Engine/Game.h" #include "../Engine/Surface.h" +#include "../Engine/SurfaceSet.h" #include "../Engine/LocalizedText.h" #include "../Engine/Unicode.h" #include "../Interface/Text.h" @@ -177,8 +179,9 @@ namespace OpenXcom _image = new Surface(32, 48, 157, 5); add(_image); - item->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), _image); - + const auto& mod = *_game->getMod(); + auto itemSprite = InventoryItemSprite(*item, mod, *_image, InventoryItemSprite::getHandCenteredSpriteBounds(*item)); + itemSprite.draw(*const_cast(mod).getSurfaceSet("BIGOBS.PCK"), InventorySpriteContext::UFOPEDIA_ARTICLE); int ammoSlot = defs->getAmmoSlotForPage(_state->current_page); int ammoSlotPrevUsage = defs->getAmmoSlotPrevUsageForPage(_state->current_page); @@ -381,7 +384,9 @@ namespace OpenXcom addAmmoDamagePower(currShow, type); - type->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), _imageAmmo[currShow]); + const auto& mod = *_game->getMod(); + auto itemSprite = InventoryItemSprite(*type, mod, *_imageAmmo[currShow], InventoryItemSprite::getHandCenteredSpriteBounds(*type)); + itemSprite.draw(*const_cast(mod).getSurfaceSet("BIGOBS.PCK"), InventorySpriteContext::UFOPEDIA_ARTICLE); ++currShow; if (currShow == maxShow) From 54855bfea31a125a6b4cfedc88ef182f166aa63b Mon Sep 17 00:00:00 2001 From: Austin Stanley Date: Sat, 22 Apr 2023 22:42:31 -0500 Subject: [PATCH 3/3] Requested changes to project files. --- src/CMakeLists.txt | 2 ++ src/OpenXcom.2010.vcxproj.filters | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 663249f2db..ee0e495815 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,7 @@ set ( battlescape_src Battlescape/InfoboxOKState.cpp Battlescape/InfoboxState.cpp Battlescape/Inventory.cpp + Battlescape/InventoryItemSprite.cpp Battlescape/InventoryLoadState.cpp Battlescape/InventoryPersonalState.cpp Battlescape/InventorySaveState.cpp @@ -115,6 +116,7 @@ set ( battlescape_src Battlescape/ScannerState.cpp Battlescape/ScannerView.cpp Battlescape/SkillMenuState.cpp + Battlescape/SpriteOverlay.cpp Battlescape/TileEngine.cpp Battlescape/TurnDiaryState.cpp Battlescape/UnitDieBState.cpp diff --git a/src/OpenXcom.2010.vcxproj.filters b/src/OpenXcom.2010.vcxproj.filters index 7ec1035208..bdcda7c8e4 100644 --- a/src/OpenXcom.2010.vcxproj.filters +++ b/src/OpenXcom.2010.vcxproj.filters @@ -1133,6 +1133,12 @@ Savegame + + Battlescape + + + Battlescape + @@ -2327,6 +2333,12 @@ Savegame + + Battlescape + + + Battlescape +