Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of scriptable sprite overlays. #120

Draft
wants to merge 3 commits into
base: oxce-plus
Choose a base branch
from

Conversation

MaxMahem
Copy link

Okay while I could keep fiddling with this forever, I think it's ready for review.

Notes on my approach:

  • This introduces two new classes, InventoryItemSprite and SpriteOverlay.
  • InventoryItemSprite is similar to ItemSprite and UnitSprite. It takes on all responsibility for drawing InventoryItems/bigobs. It is non-owning and should not leave the local scope. This was necessary to cleanly provide a centralized way to enable or disable the rendering of various (now) optional behaviors. It also eliminates a small amount of code duplication. This also represents the biggest structural changes to the code since the logic that powered these effects is removed and moved into this class.
  • InventoryItemSprite depends on a small struct, InventorySpriteContext to inform the scripting engine of the current context in which the inventory sprite is being rendered and allows for the scripts to instruct the class to render or not-render the various options.
  • InventorySpriteContext also contains a set of static values representing the current OXCE behavior (Wound overlays are only drawn on units on the ground. Ammo counts do not show up in the inventory screen). I believe there is a good argument for changing some of these defaults, but I have left it as is for now. I considered exposing it to the rule files but decided it was too much work/too intrusive, and the behavior can be adjusted by scripting anyways.
  • InventoryItemSprite itself has no exposure to scripting, which is all handled by SpriteOverlay and InventorySpriteContext
  • Most scripting behavior is handled by free functions that have been friended, and tucked away in their own namespace. This lets them access private data where necessary but does not blot up the API with calls that are not relevant outside the scripting language.
  • SpriteOverlay is the primary interface for the scripting engine and holds most of the state information needed to perform the scripting (primarily the sprite bounds and the target surface).
  • Calling the scripting is straightforward, instance a new SpriteOverlay in the local scope (it should not leave the local scope and is non-owning of any resource) and then call the draw method on it. draw is a template function and providing the appropriate templates lets the appropriate scripts be called with the appropriate arguments.
  • There are 4 new scripting hooks added by this patch at this time:
    • inventorySpriteOverlay which is called in the context of rendering an inventory sprite (anywhere it might be rendered, except the UFOpedia at this time). It exposes BattleItem data.
    • handOverlay which is called in the context of rendering a hand-slot, which is commonly bigger than the sprite. Handslot sized render boxes are also used for the hover ammo box and items selected by the cursor, so it is also called in these contexts, though it has no effect by default. It exposes BattleItem data.
    • unitPaperdollOverlay which is called when the paperdoll is rendered. It exposes Unit data.
    • unitRankOverlay which is called when the unit rank is displayed on the battlescape.

I think that about covers it. I had other things in the past, but on further consideration, I backed off from them so this is pretty much only exactly what it says on the tin. There are a few additional scripting methods thrown in that I found necessary to generate the demo effects, which probably means they are generally useful regardless.

Anyways, I had fun making this, and am personally finding a lot of these enhancements useful.

@MaxMahem MaxMahem marked this pull request as draft April 13, 2023 03:59
@MaxMahem
Copy link
Author

After some discussions with SupSuper I may have come up with an even better way of doing this, so I've converted this to a draft as I explore that.

Copy link
Collaborator

@Yankes Yankes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial batch of review

@@ -5846,6 +5846,10 @@ void setFireScript(BattleUnit *bu, int val)
}
}

void getStatusScript(const BattleUnit* bu, int& status)
{
status = bu ? bu->getStatus() : status;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result value should be deterministic, now is "random", set 0 or any other value when nullptr was given.

@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No guard for nullptr

auto* enviro = sbg->getEnviroEffects();
auto* mod = const_cast<Mod*>(sbg->getMod());
shockIndicator = enviro && !enviro->getInventoryShockIndicator().empty() ? mod->getSurface(enviro->getInventoryShockIndicator(), false)
: mod->getSurface("BigShockIndicator", false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting (good to keep 80 line limit, especially when you have ?:)

src/Mod/Mod.cpp Outdated
*/
void getInterfaceElementColor(const Mod* mod, int &color, const std::string& interfaceName, const std::string& elementName)
{
auto interface = mod->getInterface(interfaceName);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none nullptr guard

src/Mod/Mod.cpp Outdated
*/
void getInterfaceElementColor2(const Mod* mod, int& color, const std::string& interfaceName, const std::string& elementName)
{
auto interface = mod->getInterface(interfaceName);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none nullptr guard

class SavedBattleGame;

/// Namespace for segregating SpriteOverlay scripting functions.
namespace SpriteOverlayScript
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not simply make it member functions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was twofold.

  1. I didn't want to expose general consumption methods that probably shouldn't be called outside the scripting context. That is, all of these draw and blit methods really shouldn't be called by other C++ code. Such behavior is probably incorrect. But, for convenience's sake, I wanted to give them access to private data and avoid writing many pointless getter functions. friend-ing the class seemed to be the best way to compromise these two goals.
  2. Less so for this method, but for the InventorySpriteContext, I was exposing enums in funky ways (as ints) for the scripting that I normally wouldn't. Again I wanted to conceal this from the general consumption code, so I applied the same method. These properties are public so it probably could have been just free functions instead, but I thought I would stick with this approach.

I used normal member functions in the (few) cases where I thought it was reasonable for both scripting and general code to use the same method.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it could be private members, as ScriptRegister is part of class it can access private bits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't realize that. Rewrite with this in mind incoming.

/// 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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none nullptr guard

/// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none nullptr guard

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{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check mods like WH40k use bigger hands items than normally allowed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested. The mod uses a custom sprite that fakes the appearance of a bigger box for hand items. The current behavior is maintained for these items, though this patch offers some options for handling customizations in these cases.

@Yankes Yankes self-assigned this Apr 13, 2023
@MeridianOXC
Copy link
Owner

update of src/OpenXcom.2010.vcxproj.filters is missing for display in Visual Studio

update of src/CMakeLists.txt is missing for compilation on Linux, Mac and Android

@MaxMahem MaxMahem closed this Apr 23, 2023
@MaxMahem MaxMahem deleted the temp branch April 23, 2023 02:37
@MaxMahem MaxMahem restored the temp branch April 23, 2023 02:38
@MaxMahem MaxMahem reopened this Apr 23, 2023
@MaxMahem
Copy link
Author

Sorry for the spam, didn't mean to close this. Had to update the filter file manual, could not for the life of me get visual studio to do it.

@Yankes
Copy link
Collaborator

Yankes commented May 7, 2023

In week or two I will go back to this PR and analyze it in detail and possibly alter it a bit and merge manually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants