From 00e1ce973e344fb33c1af4bfb8cccc0180e2a28b Mon Sep 17 00:00:00 2001 From: comradeshook Date: Sun, 17 Nov 2024 23:17:23 +0100 Subject: [PATCH 01/12] Implemented MOSprite:GetPixelIndex(), implemented first take on MOSprite:GetAllPixelPositions() --- Source/Entities/MOSprite.cpp | 17 +++++++++++++++++ Source/Entities/MOSprite.h | 4 ++++ Source/Lua/LuaBindingsEntities.cpp | 2 ++ 3 files changed, 23 insertions(+) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 235176c0b2..43aac6196b 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -351,6 +351,23 @@ Vector MOSprite::UnRotateOffset(const Vector& offset) const { return rotOff; } +std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame) { + std::vector* posList = new std::vector(); + CLAMP(m_FrameCount - 1, 0, whichFrame); + BITMAP* sprite = m_aSprite[whichFrame]; + for (int y = 0; y < GetSpriteHeight(); y++) { + for (int x = 0; x < GetSpriteWidth(); x++) { + int pixelIndex = GetPixelIndex(x, y, whichFrame); + if (includeTransparency || pixelIndex > 0) { + Vector pixelPos = (Vector(x, y) + m_SpriteOffset).FlipX(hflipped).RadRotate(angle) + origin; + posList->push_back(pixelPos.GetRounded()); + } + } + } + + return posList; +} + void MOSprite::Update() { MovableObject::Update(); diff --git a/Source/Entities/MOSprite.h b/Source/Entities/MOSprite.h index b5b1fe1ae9..0179df3165 100644 --- a/Source/Entities/MOSprite.h +++ b/Source/Entities/MOSprite.h @@ -90,6 +90,10 @@ namespace RTE { /// Ownership is NOT transferred! BITMAP* GetSpriteFrame(unsigned int whichFrame = 0) const { return (whichFrame < m_FrameCount) ? m_aSprite[whichFrame] : 0; } + int GetPixelIndex(int x, int y, unsigned int whichFrame = 0) const { return getpixel(m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)], x, y); } + + std::vector* GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame); + /// Gets the width of the bitmap of this MOSprite /// @return Sprite width if loaded. int GetSpriteWidth() const { return m_aSprite[0] ? m_aSprite[0]->w : 0; } diff --git a/Source/Lua/LuaBindingsEntities.cpp b/Source/Lua/LuaBindingsEntities.cpp index dc148bfeae..23acf330f8 100644 --- a/Source/Lua/LuaBindingsEntities.cpp +++ b/Source/Lua/LuaBindingsEntities.cpp @@ -836,6 +836,8 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MOSprite) { .def("SetExitWound", &MOSprite::SetExitWound) .def("GetEntryWoundPresetName", &MOSprite::GetEntryWoundPresetName) .def("GetExitWoundPresetName", &MOSprite::GetExitWoundPresetName) + .def("GetPixelIndex", &MOSprite::GetPixelIndex) + .def("GetAllPixelPositions", &MOSprite::GetAllPixelPositions, luabind::return_stl_iterator) .enum_("SpriteAnimMode")[luabind::value("NOANIM", SpriteAnimMode::NOANIM), luabind::value("ALWAYSLOOP", SpriteAnimMode::ALWAYSLOOP), From 088d093a8abc962c0350e25663bc6592b8b2c865 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Mon, 18 Nov 2024 18:20:14 +0100 Subject: [PATCH 02/12] Fully implemented MOSprite:GetAllPixelPositions() --- Source/Entities/MOSprite.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 43aac6196b..05f3d3a4a4 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -355,16 +355,28 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float std::vector* posList = new std::vector(); CLAMP(m_FrameCount - 1, 0, whichFrame); BITMAP* sprite = m_aSprite[whichFrame]; - for (int y = 0; y < GetSpriteHeight(); y++) { - for (int x = 0; x < GetSpriteWidth(); x++) { - int pixelIndex = GetPixelIndex(x, y, whichFrame); + BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter * m_Scale, m_SpriteDiameter * m_Scale); + rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); + Vector offset = Vector(temp->w / 2 + m_SpriteOffset.m_X, temp->h / 2 + m_SpriteOffset.m_Y); + + if (!hflipped) { + rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + } else { + rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + } + + for (int y = 0; y < temp->h; y++) { + for (int x = 0; x < temp->w; x++) { + int pixelIndex = getpixel(temp, x, y); if (includeTransparency || pixelIndex > 0) { - Vector pixelPos = (Vector(x, y) + m_SpriteOffset).FlipX(hflipped).RadRotate(angle) + origin; + Vector pixelPos = (Vector(x - temp->w / 2, y - temp->h / 2)) + origin; posList->push_back(pixelPos.GetRounded()); } } } + destroy_bitmap(temp); + temp = NULL; return posList; } From 98445c9c0957a18995fea424d70adf35b0b36a5e Mon Sep 17 00:00:00 2001 From: comradeshook Date: Mon, 18 Nov 2024 22:19:33 +0100 Subject: [PATCH 03/12] Added bool m_SpriteModified to MOSprites to check whether or not their sprites have been modified so the extras can be deleted on Destroy Added documentation in MOSprite.h Implemented MOSprite:SetPixelIndex() Implemented MOSprite:GetAllVisiblePixelPositions() Fixed GetAllPixelPositions not accounting for sprite offset properly Exposed to Lua as necessary Changed GetAllPixelPositions to have an ignoreIndex and invert bool instead of an ignoreTransparency bool --- Source/Entities/MOSprite.cpp | 43 +++++++++++++++++++++++++----- Source/Entities/MOSprite.h | 30 ++++++++++++++++++++- Source/Lua/LuaBindingsEntities.cpp | 2 ++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 05f3d3a4a4..b878644bde 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -42,6 +42,7 @@ void MOSprite::Clear() { m_SettleMaterialDisabled = false; m_pEntryWound = 0; m_pExitWound = 0; + m_SpriteModified = false; } int MOSprite::Create() { @@ -235,6 +236,12 @@ void MOSprite::Destroy(bool notInherited) { // delete m_pEntryWound; Not doing this anymore since we're not owning // delete m_pExitWound; + if (m_SpriteModified) { + for (BITMAP* sprite : m_aSprite) { + destroy_bitmap(sprite); + } + } + if (!notInherited) MovableObject::Destroy(); Clear(); @@ -351,13 +358,36 @@ Vector MOSprite::UnRotateOffset(const Vector& offset) const { return rotOff; } -std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame) { +void MOSprite::SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + if (!m_SpriteModified) { + std::vector spriteList; + + for (BITMAP* sprite: m_aSprite) { + BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); + rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); + draw_sprite(spriteCopy, sprite, 0, 0); + spriteList.push_back(spriteCopy); + } + + m_aSprite = spriteList; + m_SpriteModified = true; + } + + BITMAP* targetSprite = m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)]; + if (ignoreIndex < 0 || (getpixel(targetSprite, x, y) == ignoreIndex) != invert) { + putpixel(targetSprite, x, y, colorIndex); + } +} + +std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert) { std::vector* posList = new std::vector(); CLAMP(m_FrameCount - 1, 0, whichFrame); BITMAP* sprite = m_aSprite[whichFrame]; - BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter * m_Scale, m_SpriteDiameter * m_Scale); + BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter, m_SpriteDiameter); rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); - Vector offset = Vector(temp->w / 2 + m_SpriteOffset.m_X, temp->h / 2 + m_SpriteOffset.m_Y); + Vector tempCentre = Vector(temp->w / 2, temp->h / 2); + Vector spriteCentre = Vector(sprite->w / 2, sprite->h / 2); + Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); if (!hflipped) { rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); @@ -368,15 +398,14 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float for (int y = 0; y < temp->h; y++) { for (int x = 0; x < temp->w; x++) { int pixelIndex = getpixel(temp, x, y); - if (includeTransparency || pixelIndex > 0) { - Vector pixelPos = (Vector(x - temp->w / 2, y - temp->h / 2)) + origin; - posList->push_back(pixelPos.GetRounded()); + if (pixelIndex >= 0 && (pixelIndex != ignoreIndex) != invert) { + Vector pixelPos = (Vector(x, y) - tempCentre) + origin; + posList->push_back(pixelPos); } } } destroy_bitmap(temp); - temp = NULL; return posList; } diff --git a/Source/Entities/MOSprite.h b/Source/Entities/MOSprite.h index 0179df3165..88c00a260b 100644 --- a/Source/Entities/MOSprite.h +++ b/Source/Entities/MOSprite.h @@ -90,9 +90,35 @@ namespace RTE { /// Ownership is NOT transferred! BITMAP* GetSpriteFrame(unsigned int whichFrame = 0) const { return (whichFrame < m_FrameCount) ? m_aSprite[whichFrame] : 0; } + /// Gets the color index of the pixel at position (X, Y) in the sprite bitmap + /// @param x X coordinate on the bitmap of the pixel to get. + /// @param y Y coordinate on the bitmap of the pixel to get. + /// @param whichFrame Which frame of the sprite sequence to check. + /// @return Color index of the indicated pixel. int GetPixelIndex(int x, int y, unsigned int whichFrame = 0) const { return getpixel(m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)], x, y); } - std::vector* GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame); + /// Sets the color index of the pixel at position (X, Y) in the sprite bitmap + /// @param x X coordinate on the bitmap of the pixel to set. + /// @param y Y coordinate on the bitmap of the pixel to set. + /// @param whichFrame Which frame of the sprite sequence to affect. + /// @param colorIndex Desired color index of the indicated pixel. + /// @param ignoreIndex Avoid setting pixel colour if it has this color index; set below 0 to disable. + /// @param invert Whether or not to invert the ignoreIndex so it ONLY colors that index. + void SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert); + + /// Returns a list of vectors pointing to all matching pixels of the given frame in the sprite, accounting for flipping, rotation and scale. + /// @param origin The position around which the vectors are centered. + /// @param angle The angle at which the sprite is rotated. + /// @param hflipped Whether or not the sprite is flipped horizontally. + /// @param whichFrame Which frame of the sprite sequence to check. + /// @param ignoreIndex Which color index to ignore when checking; set below 0 to include everything. + /// @param invert Whether or not to invert the above check so it ONLY counts that index. + /// @return List of vectors pointing to all visible pixels of the given frame in the sprite. + std::vector* GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert); + + /// Returns a list of vectors pointing to all visible pixels of the given frame in the sprite, accounting for flipping, rotation and scale. + /// @return List of vectors pointing to all visible pixels of the given frame in the sprite. + std::vector* GetAllVisiblePixelPositions() { return GetAllPixelPositions(m_Pos, m_Rotation.GetRadAngle(), m_HFlipped, m_Frame, 0, false); }; /// Gets the width of the bitmap of this MOSprite /// @return Sprite width if loaded. @@ -345,6 +371,8 @@ namespace RTE { const AEmitter* m_pEntryWound; // Exit wound template const AEmitter* m_pExitWound; + // Whether or not the sprite has been modified + bool m_SpriteModified; /// Private member variable and method declarations private: diff --git a/Source/Lua/LuaBindingsEntities.cpp b/Source/Lua/LuaBindingsEntities.cpp index 23acf330f8..d59ed81bf8 100644 --- a/Source/Lua/LuaBindingsEntities.cpp +++ b/Source/Lua/LuaBindingsEntities.cpp @@ -837,6 +837,8 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MOSprite) { .def("GetEntryWoundPresetName", &MOSprite::GetEntryWoundPresetName) .def("GetExitWoundPresetName", &MOSprite::GetExitWoundPresetName) .def("GetPixelIndex", &MOSprite::GetPixelIndex) + .def("SetPixelIndex", &MOSprite::SetPixelIndex) + .def("GetAllVisiblePixelPositions", &MOSprite::GetAllVisiblePixelPositions, luabind::return_stl_iterator) .def("GetAllPixelPositions", &MOSprite::GetAllPixelPositions, luabind::return_stl_iterator) .enum_("SpriteAnimMode")[luabind::value("NOANIM", SpriteAnimMode::NOANIM), From ec01597d219fe44d9e2170ca87da65a49f9a01b1 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Sun, 24 Nov 2024 17:05:45 +0100 Subject: [PATCH 04/12] Updated changelog, added Materal::GetColorIndex(), updated MOSprite bitmap manipulation functions sorry i can't remember all the the changes but they're good i promise --- CHANGELOG.md | 18 ++++++ Source/Entities/MOSprite.cpp | 88 ++++++++++++++++++++---------- Source/Entities/MOSprite.h | 38 ++++++++----- Source/Entities/Material.h | 4 ++ Source/Lua/LuaBindingsEntities.cpp | 14 +++-- 5 files changed, 115 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88eb0c3fa..731b336b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `Attachable` INI and Lua (R/W) properties `InheritsVelWhenDetached` and `InheritsAngularVelWhenDetached`, which determine how much of these velocities an attachable inherits from its parent when detached. Defaults to 1. +- Added Lua-accessible bitmap manipulation functions to `MOSprite`s: + ``` + GetSpritePixelIndex(int x, int y, int whichFrame) - Returns the color index of the pixel at the given coordinate on the given frame of the sprite ((0, 0) is the upper left corner!) + + SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) - Sets the color of the pixel at the given coordinate on thegiven frame of the sprite, skipping if the pixel has same color index as given in ignoreIndex. If invert is set to true, only pixels of that color index are set. + + GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) - Returns a list of vectors pointing to the absolute positions of all pixels in the given frame of the sprite, rotated to match angle, flipped to match hflipped and positioned around origin, providing a full silhouette of the MOSprite. IgnoreIndex and invert are like above, include children denotes whether or not to include all children of the MOSprite (no effect if not at least an MOSRotating). + + GetAllVisibleSpritePixelPositions(bool includeChildren) - Simplified version of the above, returning a list of absolute positions of the visible pixels of the current frame of the sprite as it is currently drawn. + + SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int ignoreIndex, bool invert) - Sets all pixels in the given frame of the sprite to the given color index, ignoring and inverting as above. + + SetAllVisibleSpritePixelIndexes(int colorIndex) - Simplified version of the above, sets all visible pixels of the currently visible sprite to the given color index. + ``` +- Added `Material` Lua function `GetColorIndex()`, which returns the color index of the calling material. + - New `ACraft` INI and Lua (R/W) property `CanEnterOrbit`, which determines whether a craft can enter orbit (and refund gold appropriately) or not. If false, default out-of-bounds deletion logic applies. @@ -149,6 +165,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixed an issue where internal Lua functions OriginalDoFile, OriginalLoadFile, and OriginalRequire were polluting the global namespace. They have now been made inaccessible. +- Fixed `MOSprite:UnRotateOffset()` giving the wrong results on HFLipped sprites. + - Various fixes and improvements to inventory management when dual-wielding or carrying a shield, to stop situations where the actor unexpectedly puts their items away. - Fixed issue where MOSR `Gib`s, `AEmitter` or `PEmitter` `Emission`s, and MetaMan `Player`s were not correctly accessible from script. diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index b878644bde..87aecbe591 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -353,51 +353,47 @@ Vector MOSprite::RotateOffset(const Vector& offset) const { } Vector MOSprite::UnRotateOffset(const Vector& offset) const { - Vector rotOff(offset.GetXFlipped(m_HFlipped)); + Vector rotOff(offset); rotOff /= const_cast(m_Rotation); - return rotOff; + return rotOff.GetXFlipped(m_HFlipped); } -void MOSprite::SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert) { - if (!m_SpriteModified) { - std::vector spriteList; - - for (BITMAP* sprite: m_aSprite) { - BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); - rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); - draw_sprite(spriteCopy, sprite, 0, 0); - spriteList.push_back(spriteCopy); - } - - m_aSprite = spriteList; - m_SpriteModified = true; - } - - BITMAP* targetSprite = m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)]; - if (ignoreIndex < 0 || (getpixel(targetSprite, x, y) == ignoreIndex) != invert) { - putpixel(targetSprite, x, y, colorIndex); +int MOSprite::GetSpritePixelIndex(int x, int y, int whichFrame) const { + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + if (is_inside_bitmap(targetSprite, x, y, 0)) { + return _getpixel(targetSprite, x, y); } + return -1; } -std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert) { +std::vector* MOSprite::GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) { std::vector* posList = new std::vector(); - CLAMP(m_FrameCount - 1, 0, whichFrame); - BITMAP* sprite = m_aSprite[whichFrame]; - BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter, m_SpriteDiameter); + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + int spriteSize = m_SpriteDiameter; + if (includeChildren && dynamic_cast(this)) { + spriteSize = dynamic_cast(this)->GetDiameter(); + } + BITMAP* sprite = m_aSprite[clampedFrame]; + BITMAP* temp = create_bitmap_ex(8, spriteSize, spriteSize); rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); Vector tempCentre = Vector(temp->w / 2, temp->h / 2); Vector spriteCentre = Vector(sprite->w / 2, sprite->h / 2); - Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); - if (!hflipped) { - rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + if (includeChildren) { + Draw(temp, m_Pos - tempCentre); } else { - rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); + if (!hflipped) { + rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + } else { + rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + } } for (int y = 0; y < temp->h; y++) { for (int x = 0; x < temp->w; x++) { - int pixelIndex = getpixel(temp, x, y); + int pixelIndex = _getpixel(temp, x, y); if (pixelIndex >= 0 && (pixelIndex != ignoreIndex) != invert) { Vector pixelPos = (Vector(x, y) - tempCentre) + origin; posList->push_back(pixelPos); @@ -409,6 +405,40 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float return posList; } +bool MOSprite::SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + if (!m_SpriteModified) { + std::vector spriteList; + + for (BITMAP* sprite : m_aSprite) { + BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); + rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); + draw_sprite(spriteCopy, sprite, 0, 0); + spriteList.push_back(spriteCopy); + } + + m_aSprite = spriteList; + m_SpriteModified = true; + } + + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + if (is_inside_bitmap(targetSprite, x, y, 0) && (ignoreIndex < 0 || (_getpixel(targetSprite, x, y) != ignoreIndex) != invert)) { + _putpixel(targetSprite, x, y, colorIndex); + return true; + } + return false; +} + +void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + for (int y = 0; y < targetSprite->h; y++) { + for (int x = 0; x < targetSprite->w; x++) { + SetSpritePixelIndex(x, y, clampedFrame, colorIndex, ignoreIndex, invert); + } + } +} + void MOSprite::Update() { MovableObject::Update(); diff --git a/Source/Entities/MOSprite.h b/Source/Entities/MOSprite.h index 88c00a260b..7f9b690952 100644 --- a/Source/Entities/MOSprite.h +++ b/Source/Entities/MOSprite.h @@ -95,30 +95,42 @@ namespace RTE { /// @param y Y coordinate on the bitmap of the pixel to get. /// @param whichFrame Which frame of the sprite sequence to check. /// @return Color index of the indicated pixel. - int GetPixelIndex(int x, int y, unsigned int whichFrame = 0) const { return getpixel(m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)], x, y); } - - /// Sets the color index of the pixel at position (X, Y) in the sprite bitmap - /// @param x X coordinate on the bitmap of the pixel to set. - /// @param y Y coordinate on the bitmap of the pixel to set. - /// @param whichFrame Which frame of the sprite sequence to affect. - /// @param colorIndex Desired color index of the indicated pixel. - /// @param ignoreIndex Avoid setting pixel colour if it has this color index; set below 0 to disable. - /// @param invert Whether or not to invert the ignoreIndex so it ONLY colors that index. - void SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert); + int GetSpritePixelIndex(int x, int y, int whichFrame = 0) const; /// Returns a list of vectors pointing to all matching pixels of the given frame in the sprite, accounting for flipping, rotation and scale. - /// @param origin The position around which the vectors are centered. + /// @param origin The absolute position around which the vectors are centered. /// @param angle The angle at which the sprite is rotated. /// @param hflipped Whether or not the sprite is flipped horizontally. /// @param whichFrame Which frame of the sprite sequence to check. /// @param ignoreIndex Which color index to ignore when checking; set below 0 to include everything. /// @param invert Whether or not to invert the above check so it ONLY counts that index. /// @return List of vectors pointing to all visible pixels of the given frame in the sprite. - std::vector* GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert); + std::vector* GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren); /// Returns a list of vectors pointing to all visible pixels of the given frame in the sprite, accounting for flipping, rotation and scale. /// @return List of vectors pointing to all visible pixels of the given frame in the sprite. - std::vector* GetAllVisiblePixelPositions() { return GetAllPixelPositions(m_Pos, m_Rotation.GetRadAngle(), m_HFlipped, m_Frame, 0, false); }; + std::vector* GetAllVisibleSpritePixelPositions(bool includeChildren) { return GetAllSpritePixelPositions(m_Pos, m_Rotation.GetRadAngle(), m_HFlipped, m_Frame, 0, false, includeChildren); }; + + /// Sets the color index of the pixel at position (X, Y) in the sprite bitmap + /// @param x X coordinate on the bitmap of the pixel to set. + /// @param y Y coordinate on the bitmap of the pixel to set. + /// @param whichFrame Which frame of the sprite sequence to affect. + /// @param colorIndex Desired color index of the indicated pixel. + /// @param ignoreIndex Avoid setting pixel colour if it has this color index; set below 0 to disable. + /// @param invert Whether or not to invert the ignoreIndex so it ONLY colors that index. + /// @return Whether or not the pixel index was successfully set. + bool SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert); + + /// Sets the color index of all matching pixels in the sprite bitmap. + /// @param whichFrame Which frame of the sprite sequence to affect. + /// @param colorIndex Desired color index of the pixels. + /// @param ignoreIndex Avoid setting pixel colour if it has this color index; set below 0 to disable. + /// @param invert Whether or not to invert the ignoreIndex so it ONLY colors that index. + void SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int ignoreIndex, bool invert); + + /// Sets the color index of all visible pixels in the sprite bitmap. + /// @param colorIndex Desired color index of the pixels. + void SetAllVisibleSpritePixelIndexes(int colorIndex) { SetAllSpritePixelIndexes(m_Frame, colorIndex, 0, false); }; /// Gets the width of the bitmap of this MOSprite /// @return Sprite width if loaded. diff --git a/Source/Entities/Material.h b/Source/Entities/Material.h index 20e055fee8..040bf340dc 100644 --- a/Source/Entities/Material.h +++ b/Source/Entities/Material.h @@ -108,6 +108,10 @@ namespace RTE { /// @return The color of this material. Color GetColor() const { return m_Color; } + /// Gets the color index of this Material. + /// @return The color index of this material. + int GetColorIndex() const { return m_Color.GetIndex(); } + /// Indicates whether or not to use the Material's own color when a pixel of this Material is knocked loose from the terrain. /// @return Whether the Material's color, or the terrain pixel's color should be applied. bool UsesOwnColor() const { return m_UseOwnColor; } diff --git a/Source/Lua/LuaBindingsEntities.cpp b/Source/Lua/LuaBindingsEntities.cpp index d59ed81bf8..a71c476cbc 100644 --- a/Source/Lua/LuaBindingsEntities.cpp +++ b/Source/Lua/LuaBindingsEntities.cpp @@ -774,7 +774,9 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, Material) { .property("SettleMaterial", &Material::GetSettleMaterial) .property("SpawnMaterial", &Material::GetSpawnMaterial) .property("TransformsInto", &Material::GetSpawnMaterial) - .property("IsScrap", &Material::IsScrap); + .property("IsScrap", &Material::IsScrap) + + .def("GetColorIndex", &Material::GetColorIndex); } LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MetaPlayer) { @@ -836,10 +838,12 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MOSprite) { .def("SetExitWound", &MOSprite::SetExitWound) .def("GetEntryWoundPresetName", &MOSprite::GetEntryWoundPresetName) .def("GetExitWoundPresetName", &MOSprite::GetExitWoundPresetName) - .def("GetPixelIndex", &MOSprite::GetPixelIndex) - .def("SetPixelIndex", &MOSprite::SetPixelIndex) - .def("GetAllVisiblePixelPositions", &MOSprite::GetAllVisiblePixelPositions, luabind::return_stl_iterator) - .def("GetAllPixelPositions", &MOSprite::GetAllPixelPositions, luabind::return_stl_iterator) + .def("GetSpritePixelIndex", &MOSprite::GetSpritePixelIndex) + .def("SetSpritePixelIndex", &MOSprite::SetSpritePixelIndex) + .def("GetAllSpritePixelPositions", &MOSprite::GetAllSpritePixelPositions, luabind::return_stl_iterator) + .def("GetAllVisibleSpritePixelPositions", &MOSprite::GetAllVisibleSpritePixelPositions, luabind::return_stl_iterator) + .def("SetAllSpritePixelIndexes", &MOSprite::SetAllSpritePixelIndexes) + .def("SetAllVisibleSpritePixelIndexes", &MOSprite::SetAllVisibleSpritePixelIndexes) .enum_("SpriteAnimMode")[luabind::value("NOANIM", SpriteAnimMode::NOANIM), luabind::value("ALWAYSLOOP", SpriteAnimMode::ALWAYSLOOP), From 1b90c30e96b854363d3eda61d99a0ef1d69f0deb Mon Sep 17 00:00:00 2001 From: comradeshook Date: Sun, 17 Nov 2024 23:17:23 +0100 Subject: [PATCH 05/12] Implemented MOSprite:GetPixelIndex(), implemented first take on MOSprite:GetAllPixelPositions() --- Source/Entities/MOSprite.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 87aecbe591..66024de8b5 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -439,6 +439,23 @@ void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int igno } } +std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame) { + std::vector* posList = new std::vector(); + CLAMP(m_FrameCount - 1, 0, whichFrame); + BITMAP* sprite = m_aSprite[whichFrame]; + for (int y = 0; y < GetSpriteHeight(); y++) { + for (int x = 0; x < GetSpriteWidth(); x++) { + int pixelIndex = GetPixelIndex(x, y, whichFrame); + if (includeTransparency || pixelIndex > 0) { + Vector pixelPos = (Vector(x, y) + m_SpriteOffset).FlipX(hflipped).RadRotate(angle) + origin; + posList->push_back(pixelPos.GetRounded()); + } + } + } + + return posList; +} + void MOSprite::Update() { MovableObject::Update(); From cd5e567631e69150aaa101be1988b9e8eae480af Mon Sep 17 00:00:00 2001 From: comradeshook Date: Mon, 18 Nov 2024 18:20:14 +0100 Subject: [PATCH 06/12] Fully implemented MOSprite:GetAllPixelPositions() --- Source/Entities/MOSprite.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 66024de8b5..ccec54f464 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -443,16 +443,28 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float std::vector* posList = new std::vector(); CLAMP(m_FrameCount - 1, 0, whichFrame); BITMAP* sprite = m_aSprite[whichFrame]; - for (int y = 0; y < GetSpriteHeight(); y++) { - for (int x = 0; x < GetSpriteWidth(); x++) { - int pixelIndex = GetPixelIndex(x, y, whichFrame); + BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter * m_Scale, m_SpriteDiameter * m_Scale); + rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); + Vector offset = Vector(temp->w / 2 + m_SpriteOffset.m_X, temp->h / 2 + m_SpriteOffset.m_Y); + + if (!hflipped) { + rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + } else { + rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + } + + for (int y = 0; y < temp->h; y++) { + for (int x = 0; x < temp->w; x++) { + int pixelIndex = getpixel(temp, x, y); if (includeTransparency || pixelIndex > 0) { - Vector pixelPos = (Vector(x, y) + m_SpriteOffset).FlipX(hflipped).RadRotate(angle) + origin; + Vector pixelPos = (Vector(x - temp->w / 2, y - temp->h / 2)) + origin; posList->push_back(pixelPos.GetRounded()); } } } + destroy_bitmap(temp); + temp = NULL; return posList; } From 2f53be3dd7995f6adfd111342eb01898b5f72b85 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Mon, 18 Nov 2024 22:19:33 +0100 Subject: [PATCH 07/12] Added bool m_SpriteModified to MOSprites to check whether or not their sprites have been modified so the extras can be deleted on Destroy Added documentation in MOSprite.h Implemented MOSprite:SetPixelIndex() Implemented MOSprite:GetAllVisiblePixelPositions() Fixed GetAllPixelPositions not accounting for sprite offset properly Exposed to Lua as necessary Changed GetAllPixelPositions to have an ignoreIndex and invert bool instead of an ignoreTransparency bool --- Source/Entities/MOSprite.cpp | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index ccec54f464..6b15a1e8b5 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -439,13 +439,36 @@ void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int igno } } -std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, bool includeTransparency, unsigned int whichFrame) { +void MOSprite::SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + if (!m_SpriteModified) { + std::vector spriteList; + + for (BITMAP* sprite: m_aSprite) { + BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); + rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); + draw_sprite(spriteCopy, sprite, 0, 0); + spriteList.push_back(spriteCopy); + } + + m_aSprite = spriteList; + m_SpriteModified = true; + } + + BITMAP* targetSprite = m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)]; + if (ignoreIndex < 0 || (getpixel(targetSprite, x, y) == ignoreIndex) != invert) { + putpixel(targetSprite, x, y, colorIndex); + } +} + +std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert) { std::vector* posList = new std::vector(); CLAMP(m_FrameCount - 1, 0, whichFrame); BITMAP* sprite = m_aSprite[whichFrame]; - BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter * m_Scale, m_SpriteDiameter * m_Scale); + BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter, m_SpriteDiameter); rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); - Vector offset = Vector(temp->w / 2 + m_SpriteOffset.m_X, temp->h / 2 + m_SpriteOffset.m_Y); + Vector tempCentre = Vector(temp->w / 2, temp->h / 2); + Vector spriteCentre = Vector(sprite->w / 2, sprite->h / 2); + Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); if (!hflipped) { rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); @@ -456,15 +479,14 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float for (int y = 0; y < temp->h; y++) { for (int x = 0; x < temp->w; x++) { int pixelIndex = getpixel(temp, x, y); - if (includeTransparency || pixelIndex > 0) { - Vector pixelPos = (Vector(x - temp->w / 2, y - temp->h / 2)) + origin; - posList->push_back(pixelPos.GetRounded()); + if (pixelIndex >= 0 && (pixelIndex != ignoreIndex) != invert) { + Vector pixelPos = (Vector(x, y) - tempCentre) + origin; + posList->push_back(pixelPos); } } } destroy_bitmap(temp); - temp = NULL; return posList; } From e4bacc0d0878ba53a12615ba8fa0f9542da7ac83 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Sun, 24 Nov 2024 17:05:45 +0100 Subject: [PATCH 08/12] Updated changelog, added Materal::GetColorIndex(), updated MOSprite bitmap manipulation functions sorry i can't remember all the the changes but they're good i promise --- Source/Entities/MOSprite.cpp | 84 ++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 6b15a1e8b5..95e503abc9 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -439,46 +439,42 @@ void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int igno } } -void MOSprite::SetPixelIndex(int x, int y, unsigned int whichFrame, int colorIndex, int ignoreIndex, bool invert) { - if (!m_SpriteModified) { - std::vector spriteList; - - for (BITMAP* sprite: m_aSprite) { - BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); - rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); - draw_sprite(spriteCopy, sprite, 0, 0); - spriteList.push_back(spriteCopy); - } - - m_aSprite = spriteList; - m_SpriteModified = true; - } - - BITMAP* targetSprite = m_aSprite[CLAMP(m_FrameCount - 1, 0, whichFrame)]; - if (ignoreIndex < 0 || (getpixel(targetSprite, x, y) == ignoreIndex) != invert) { - putpixel(targetSprite, x, y, colorIndex); +int MOSprite::GetSpritePixelIndex(int x, int y, int whichFrame) const { + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + if (is_inside_bitmap(targetSprite, x, y, 0)) { + return _getpixel(targetSprite, x, y); } + return -1; } -std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float angle, bool hflipped, unsigned int whichFrame, int ignoreIndex, bool invert) { +std::vector* MOSprite::GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) { std::vector* posList = new std::vector(); - CLAMP(m_FrameCount - 1, 0, whichFrame); - BITMAP* sprite = m_aSprite[whichFrame]; - BITMAP* temp = create_bitmap_ex(8, m_SpriteDiameter, m_SpriteDiameter); + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + int spriteSize = m_SpriteDiameter; + if (includeChildren && dynamic_cast(this)) { + spriteSize = dynamic_cast(this)->GetDiameter(); + } + BITMAP* sprite = m_aSprite[clampedFrame]; + BITMAP* temp = create_bitmap_ex(8, spriteSize, spriteSize); rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); Vector tempCentre = Vector(temp->w / 2, temp->h / 2); Vector spriteCentre = Vector(sprite->w / 2, sprite->h / 2); - Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); - if (!hflipped) { - rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + if (includeChildren) { + Draw(temp, m_Pos - tempCentre); } else { - rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); + if (!hflipped) { + rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); + } else { + rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); + } } for (int y = 0; y < temp->h; y++) { for (int x = 0; x < temp->w; x++) { - int pixelIndex = getpixel(temp, x, y); + int pixelIndex = _getpixel(temp, x, y); if (pixelIndex >= 0 && (pixelIndex != ignoreIndex) != invert) { Vector pixelPos = (Vector(x, y) - tempCentre) + origin; posList->push_back(pixelPos); @@ -490,6 +486,40 @@ std::vector* MOSprite::GetAllPixelPositions(const Vector& origin, float return posList; } +bool MOSprite::SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + if (!m_SpriteModified) { + std::vector spriteList; + + for (BITMAP* sprite : m_aSprite) { + BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); + rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); + draw_sprite(spriteCopy, sprite, 0, 0); + spriteList.push_back(spriteCopy); + } + + m_aSprite = spriteList; + m_SpriteModified = true; + } + + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + if (is_inside_bitmap(targetSprite, x, y, 0) && (ignoreIndex < 0 || (_getpixel(targetSprite, x, y) != ignoreIndex) != invert)) { + _putpixel(targetSprite, x, y, colorIndex); + return true; + } + return false; +} + +void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int ignoreIndex, bool invert) { + unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); + BITMAP* targetSprite = m_aSprite[clampedFrame]; + for (int y = 0; y < targetSprite->h; y++) { + for (int x = 0; x < targetSprite->w; x++) { + SetSpritePixelIndex(x, y, clampedFrame, colorIndex, ignoreIndex, invert); + } + } +} + void MOSprite::Update() { MovableObject::Update(); From 376158035bd338d22d4a4b3ae1d3d9a3883325f6 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Tue, 10 Dec 2024 18:23:59 +0100 Subject: [PATCH 09/12] Rebasing is a fucking mess :D --- Source/Entities/MOSprite.cpp | 81 ------------------------------------ 1 file changed, 81 deletions(-) diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 95e503abc9..87aecbe591 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -439,87 +439,6 @@ void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int igno } } -int MOSprite::GetSpritePixelIndex(int x, int y, int whichFrame) const { - unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); - BITMAP* targetSprite = m_aSprite[clampedFrame]; - if (is_inside_bitmap(targetSprite, x, y, 0)) { - return _getpixel(targetSprite, x, y); - } - return -1; -} - -std::vector* MOSprite::GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) { - std::vector* posList = new std::vector(); - unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); - int spriteSize = m_SpriteDiameter; - if (includeChildren && dynamic_cast(this)) { - spriteSize = dynamic_cast(this)->GetDiameter(); - } - BITMAP* sprite = m_aSprite[clampedFrame]; - BITMAP* temp = create_bitmap_ex(8, spriteSize, spriteSize); - rectfill(temp, 0, 0, temp->w - 1, temp->h - 1, 0); - Vector tempCentre = Vector(temp->w / 2, temp->h / 2); - Vector spriteCentre = Vector(sprite->w / 2, sprite->h / 2); - - if (includeChildren) { - Draw(temp, m_Pos - tempCentre); - } else { - Vector offset = (tempCentre + (m_SpriteOffset + spriteCentre).GetXFlipped(m_HFlipped).RadRotate(m_Rotation.GetRadAngle()) - spriteCentre); - if (!hflipped) { - rotate_scaled_sprite(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())), ftofix(m_Scale)); - } else { - rotate_scaled_sprite_v_flip(temp, sprite, offset.m_X, offset.m_Y, ftofix(GetAllegroAngle(-m_Rotation.GetDegAngle())) + itofix(128), ftofix(m_Scale)); - } - } - - for (int y = 0; y < temp->h; y++) { - for (int x = 0; x < temp->w; x++) { - int pixelIndex = _getpixel(temp, x, y); - if (pixelIndex >= 0 && (pixelIndex != ignoreIndex) != invert) { - Vector pixelPos = (Vector(x, y) - tempCentre) + origin; - posList->push_back(pixelPos); - } - } - } - - destroy_bitmap(temp); - return posList; -} - -bool MOSprite::SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) { - if (!m_SpriteModified) { - std::vector spriteList; - - for (BITMAP* sprite : m_aSprite) { - BITMAP* spriteCopy = create_bitmap_ex(8, sprite->w, sprite->h); - rectfill(spriteCopy, 0, 0, spriteCopy->w - 1, spriteCopy->h - 1, 0); - draw_sprite(spriteCopy, sprite, 0, 0); - spriteList.push_back(spriteCopy); - } - - m_aSprite = spriteList; - m_SpriteModified = true; - } - - unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); - BITMAP* targetSprite = m_aSprite[clampedFrame]; - if (is_inside_bitmap(targetSprite, x, y, 0) && (ignoreIndex < 0 || (_getpixel(targetSprite, x, y) != ignoreIndex) != invert)) { - _putpixel(targetSprite, x, y, colorIndex); - return true; - } - return false; -} - -void MOSprite::SetAllSpritePixelIndexes(int whichFrame, int colorIndex, int ignoreIndex, bool invert) { - unsigned int clampedFrame = std::max(std::min(whichFrame, static_cast(m_FrameCount) - 1), 0); - BITMAP* targetSprite = m_aSprite[clampedFrame]; - for (int y = 0; y < targetSprite->h; y++) { - for (int x = 0; x < targetSprite->w; x++) { - SetSpritePixelIndex(x, y, clampedFrame, colorIndex, ignoreIndex, invert); - } - } -} - void MOSprite::Update() { MovableObject::Update(); From ce65dc65d64a294da8eb44fbf9bb27e7189c78b4 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Sun, 5 Jan 2025 14:30:20 +0100 Subject: [PATCH 10/12] Minor changelog update for clarity --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d9156cac..814ed247f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,9 +85,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ``` GetSpritePixelIndex(int x, int y, int whichFrame) - Returns the color index of the pixel at the given coordinate on the given frame of the sprite ((0, 0) is the upper left corner!) - SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) - Sets the color of the pixel at the given coordinate on thegiven frame of the sprite, skipping if the pixel has same color index as given in ignoreIndex. If invert is set to true, only pixels of that color index are set. + SetSpritePixelIndex(int x, int y, int whichFrame, int colorIndex, int ignoreIndex, bool invert) - Sets the color of the pixel at the given coordinate on the given frame of the sprite, skipping if the pixel has same color index as given in "ignoreIndex". If "invert" is set to true, only pixels of that color index are set. - GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) - Returns a list of vectors pointing to the absolute positions of all pixels in the given frame of the sprite, rotated to match angle, flipped to match hflipped and positioned around origin, providing a full silhouette of the MOSprite. IgnoreIndex and invert are like above, include children denotes whether or not to include all children of the MOSprite (no effect if not at least an MOSRotating). + GetAllSpritePixelPositions(const Vector& origin, float angle, bool hflipped, int whichFrame, int ignoreIndex, bool invert, bool includeChildren) - Returns a list of vectors pointing to the absolute positions of all pixels in the given frame of the sprite, rotated to match "angle", flipped to match "hflipped" and positioned around "origin", providing a full silhouette of the MOSprite. "IgnoreIndex" and "invert" are like above, "includeChildren" denotes whether or not to include all children of the MOSprite (no effect if not at least an MOSRotating). GetAllVisibleSpritePixelPositions(bool includeChildren) - Simplified version of the above, returning a list of absolute positions of the visible pixels of the current frame of the sprite as it is currently drawn. From 36ac50ab93a480400692ab9ffbb39fa8c5ffd68c Mon Sep 17 00:00:00 2001 From: comradeshook Date: Tue, 7 Jan 2025 17:15:28 +0100 Subject: [PATCH 11/12] Added Lua script for deforming bullets as reference for future C++ implementation (not yet applied to anything vanilla) --- Data/Base.rte/Scripts/DeformingBullet.lua | 248 ++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 Data/Base.rte/Scripts/DeformingBullet.lua diff --git a/Data/Base.rte/Scripts/DeformingBullet.lua b/Data/Base.rte/Scripts/DeformingBullet.lua new file mode 100644 index 0000000000..793b61f7d8 --- /dev/null +++ b/Data/Base.rte/Scripts/DeformingBullet.lua @@ -0,0 +1,248 @@ +---- TO USE: +-- Simply add the script to any MO capable of wounding (most commonly MOPixel bullets). +-- Example: ScriptPath = Base.rte/DeformingBullet.lua + +-- Min/max radius of the entry wound hole, including discoloured outer ring +local entryWoundRadius = {1, 2}; + +-- Min/max radius of the exit wound hole, including discoloured outer ring +local exitWoundRadius = {2, 3}; + +-- Whether or not the wounds should count towards GibWoundLimit of the MOSR; mostly for testing +local countTowardsWoundLimit = true; + +-- How much to multiply the sharpness by for MOSR collisions only +local sharpnessMultiplier = 1; + +function Create(self) + local var = {}; + var.Pos = self.Pos; + var.Vel = self.Vel; + var.Sharpness = self.Sharpness; + var.ringPositions = {}; + var.canPenetrate = true; + var.newPos = nil; + var.newVel = nil; + var.numberOfHits = 0; + self.Sharpness = -math.abs(self.Sharpness); -- Set sharpness value to be negative to preserve terrain destruction + self.var = var; +end + +-- Returns a table with all unique colour indexes of the sprite, except transparency. +-- Used for the discoloured outer ring of the wound holes. +local function GetAllSpriteColors(MOSprite) + if (MOSprite ~= nil) then + local spriteSize = Vector(MOSprite:GetSpriteWidth()-1, MOSprite:GetSpriteHeight()-1); + local colorTable = {}; + local colorCount = 0; + for y = 0, spriteSize.Y do + for x = 0, spriteSize.X do + local pixelColor = MOSprite:GetSpritePixelIndex(x, y, MOSprite.Frame); + if (pixelColor > 0) then + if (colorCount == 0) then + colorCount = colorCount + 1; + colorTable[colorCount] = pixelColor; + else + local i = 0; + local colorFound = false; + repeat + i = i + 1; + colorFound = pixelColor == colorTable[i]; + until colorFound == true or i >= colorCount + + if (colorFound == false) then + colorCount = colorCount + 1; + colorTable[colorCount] = pixelColor; + end + end + end + end + end + + return colorTable; + else + return {}; + end +end + +-- Adds a given wound with accompanying hole in the sprite +local function addDeformWound(var, MO, radiusTable, rangeVector, absWoundPos, angleOffset, woundPresetName) + local MOSprite = ToMOSprite(MO); + local holeRadius = math.random(radiusTable[1], radiusTable[2]); + local woundEmitterOffset = Vector(holeRadius, 0):GetRadRotatedCopy(rangeVector.AbsRadAngle + angleOffset); -- Vector to push the created wound in from the new hole + local holeOffset = SceneMan:ShortestDistance(MO.Pos, absWoundPos, true); + local woundOffset = holeOffset + woundEmitterOffset; -- Push the wound MO inwards to make it visually spawn on the MO rather than thin air + local holePos = MOSprite:UnRotateOffset(holeOffset); + local woundPos = MOSprite:UnRotateOffset(woundOffset); + + -- Creates the wound at the default position if the presetname exists; script might bork if no wound is given + local newWound = nil; + if (woundPresetName ~= "") then + newWound = CreateAEmitter(woundPresetName); + local inboundAngle = rangeVector:GetXFlipped(MO.HFlipped).AbsRadAngle; + local woundAngle = inboundAngle - (MO.RotAngle * MO.FlipFactor) + math.pi + angleOffset; -- ... We should probably have an MOSprite:UnRotateAngle() function + -- newWound.Lifetime = 50; + -- newWound.BurstDamage = 0; + MO:AddWound(newWound, woundPos, countTowardsWoundLimit); + newWound.InheritedRotAngleOffset = woundAngle; + end + + -- Makes a hole in the sprite, discolouring the outermost pixels instead of removing them. + -- Iterates radially, could be made into a square with a distance check if coverage is spotty. + for i = 0, holeRadius do + local circumference = holeRadius * 2 * math.pi; + local angleStep = (math.pi*2)/circumference; + for q = 1, circumference do + local pos = Vector(i, 0):GetRadRotatedCopy(angleStep*q).Ceilinged + (holePos - MOSprite.SpriteOffset); + local color = 0; -- Default hole colour is transparent + + -- If we're at the edge of the hole and the wound has any colours, set pixel colour to a random wound colour instead of transparent + if (i == holeRadius and IsMOSprite(newWound)) then + local colorTable = GetAllSpriteColors(ToMOSprite(newWound)); + if (#colorTable > 0) then + color = colorTable[math.random(1, #colorTable)]; + end + end + + -- Change pixel colour on all frames of the sprite and, if we're at the edge, make a table of all valid positions on the outer ring + for frame = 0, MOSprite.FrameCount do + if (MOSprite:SetSpritePixelIndex(pos.X, pos.Y, frame, color, 0, false) and i == holeRadius) then + table.insert(var.ringPositions, pos + MOSprite.SpriteOffset); + end + end + end + end + + -- Attempts to displace all wound MOs within the radius to the edge of it + for wound in MO:GetWounds() do + local woundDist = wound.ParentOffset - holePos; + if (woundDist.Magnitude < holeRadius) then + -- Calculate a vector from hole centre to wound position and set it to equal the radius of the hole, pushing the wound out to the edge + local newDist = Vector(woundDist.X, woundDist.Y); + local newOffset = holePos + newDist:SetMagnitude(holeRadius); + local bitmapOffset = newOffset - MOSprite.SpriteOffset; + -- If the calculated position isn't transparent, set parentoffset to this + if (MOSprite:GetSpritePixelIndex(bitmapOffset.X, bitmapOffset.Y, MOSprite.Frame) == -2) then + wound.ParentOffset = newOffset; + else + -- If calculated position was invalid, pick a random position on the outside ring + if (#var.ringPositions > 0) then + local pos; + local bitmapPos; + local foundPixel = false; + repeat + pos = table.remove(var.ringPositions, math.random(1, #var.ringPositions)); + bitmapPos = pos - MOSprite.SpriteOffset; + foundPixel = MOSprite:GetSpritePixelIndex(bitmapPos.X, bitmapPos.Y, MOSprite.Frame) > 0; + until + #var.ringPositions <= 0 or foundPixel + + if (foundPixel) then + wound.ParentOffset = pos; + else + -- If, somehow, no valid position is found, delete the wound; this might need changing but is an edge case + wound.ToDelete = true; + end + else + -- If there are no outer ring positions, delete the wound + wound.ToDelete = true; + end + end + end + end + + return newWound; +end + +function OnCollideWithMO(self, hitMO, hitMORootParent) + local var = self.var; + + -- Calculate MOSR penetration power + local penetration = self.Mass * var.Sharpness * var.Vel.Magnitude * sharpnessMultiplier; + + -- If the target isn't about to cease existing, the bullet hasn't penetrated this frame and the material of the MO is weak enough to penetrate, proceed + if hitMO.ToDelete == false and var.canPenetrate and hitMO.Material.StructuralIntegrity <= penetration then + var.canPenetrate = false; -- Ensure this is only run once per frame + local rangeVector = var.Vel/3; + local endPos = var.Pos + rangeVector; + + -- We do already have the MO but we need the point of impact + local raycast = SceneMan:CastMORay(var.Pos, rangeVector, self.RootID, self.IgnoresWhichTeam, 0, true, 0); + + if raycast ~= 255 then + endPos = SceneMan:GetLastRayHitPos(); -- Point of impact, woo + local MO = ToMOSRotating(MovableMan:GetMOFromID(raycast)); + local MOSprite = ToMOSprite(MO); + var.ringPositions = {}; -- Reset ring position table for this collision + local maxPen = penetration / MO.Material.StructuralIntegrity; -- Max penetration depth + local penVec = rangeVector.Normalized; + local hitOffset = SceneMan:ShortestDistance(MO.Pos, endPos, true); + + -- Add the entry wound + addDeformWound(var, MO, entryWoundRadius, rangeVector, endPos, 0, MO:GetEntryWoundPresetName()); + + -- Bit of table bullshit for Lua performance; just use vectors in C++ + local startPos = {hitOffset.X, hitOffset.Y}; + local exitWoundPos = nil; + local penVecTable = {penVec.X, penVec.Y}; + local penUsed = 0; + local pixelFound = false; + -- Check for exit wound + for i = 1, maxPen do + local checkPos = Vector(startPos[1] + penVecTable[1]*i, startPos[2] + penVecTable[2]*i); + checkPos = MOSprite:UnRotateOffset(checkPos); + checkPos = checkPos - MOSprite.SpriteOffset; + local pixel = MOSprite:GetSpritePixelIndex(checkPos.X, checkPos.Y, MOSprite.Frame); + + -- If we've found a valid pixel and the iterator exits the visible sprite, add exit wound at last found pixel + if (pixelFound and pixel <= 0) then + exitWoundPos = Vector(startPos[1] + penVecTable[1]*i, startPos[2] + penVecTable[2]*i); + pixelFound = false; + end + + -- If outside of sprite dimensions, break loop + if (pixel < 0) then + break; + end + + -- If we find a visible pixel + if (pixel > 0) then + penUsed = penUsed + MO.Material.StructuralIntegrity; + pixelFound = true; + end + + -- If all penetration has been spent, break loop + if (penUsed >= penetration) then + break; + end + end + + -- If a valid exit wound position has been found, add exit wound and set bullet to appear out of this wound with appropriately reduced velocity + if (exitWoundPos) then + local exitWound = addDeformWound(var, MO, exitWoundRadius, rangeVector, exitWoundPos + MO.Pos, math.pi, MO:GetExitWoundPresetName()); + var.newVel = rangeVector * 3 * (1-(penUsed / penetration)); + var.newPos = exitWoundPos + MO.Pos; + self:SetWhichMOToNotHit(MO:GetRootParent(), 0.035); -- Makes sure the bullet only hits this MOSR once + else + self.ToDelete = true; + var.newVel = (endPos - self.Pos) / 3; -- Attempts to prevent the bullet from visually bouncing off for one frame + end + end + end +end + +function Update(self) + local var = self.var; + var.canPenetrate = true; + + -- We have to set new velocities and positions in Update because it borks in OnCollideWithMO + if (var.newVel) then + self.Vel = Vector(var.newVel.X, var.newVel.Y); + var.newVel = nil; + end + + if (var.newPos) then + self.Pos = Vector(var.newPos.X, var.newPos.Y); + var.newPos = nil; + end +end \ No newline at end of file From c4764a48605af943f240334e054e65c42760a147 Mon Sep 17 00:00:00 2001 From: comradeshook Date: Tue, 7 Jan 2025 17:15:28 +0100 Subject: [PATCH 12/12] Added Lua script for deforming bullets as reference for future C++ implementation (not yet applied to anything vanilla) --- Data/Base.rte/Scripts/DeformingBullet.lua | 248 ++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 Data/Base.rte/Scripts/DeformingBullet.lua diff --git a/Data/Base.rte/Scripts/DeformingBullet.lua b/Data/Base.rte/Scripts/DeformingBullet.lua new file mode 100644 index 0000000000..e0b366d8f5 --- /dev/null +++ b/Data/Base.rte/Scripts/DeformingBullet.lua @@ -0,0 +1,248 @@ +---- TO USE: +-- Simply add the script to any MO capable of wounding (most commonly MOPixel bullets). +-- Example: ScriptPath = Base.rte/Scripts/DeformingBullet.lua + +-- Min/max radius of the entry wound hole, including discoloured outer ring +local entryWoundRadius = {1, 2}; + +-- Min/max radius of the exit wound hole, including discoloured outer ring +local exitWoundRadius = {2, 3}; + +-- Whether or not the wounds should count towards GibWoundLimit of the MOSR; mostly for testing +local countTowardsWoundLimit = true; + +-- How much to multiply the sharpness by for MOSR collisions only +local sharpnessMultiplier = 1; + +function Create(self) + local var = {}; + var.Pos = self.Pos; + var.Vel = self.Vel; + var.Sharpness = self.Sharpness; + var.ringPositions = {}; + var.canPenetrate = true; + var.newPos = nil; + var.newVel = nil; + var.numberOfHits = 0; + self.Sharpness = -math.abs(self.Sharpness); -- Set sharpness value to be negative to preserve terrain destruction + self.var = var; +end + +-- Returns a table with all unique colour indexes of the sprite, except transparency. +-- Used for the discoloured outer ring of the wound holes. +local function GetAllSpriteColors(MOSprite) + if (MOSprite ~= nil) then + local spriteSize = Vector(MOSprite:GetSpriteWidth()-1, MOSprite:GetSpriteHeight()-1); + local colorTable = {}; + local colorCount = 0; + for y = 0, spriteSize.Y do + for x = 0, spriteSize.X do + local pixelColor = MOSprite:GetSpritePixelIndex(x, y, MOSprite.Frame); + if (pixelColor > 0) then + if (colorCount == 0) then + colorCount = colorCount + 1; + colorTable[colorCount] = pixelColor; + else + local i = 0; + local colorFound = false; + repeat + i = i + 1; + colorFound = pixelColor == colorTable[i]; + until colorFound == true or i >= colorCount + + if (colorFound == false) then + colorCount = colorCount + 1; + colorTable[colorCount] = pixelColor; + end + end + end + end + end + + return colorTable; + else + return {}; + end +end + +-- Adds a given wound with accompanying hole in the sprite +local function addDeformWound(var, MO, radiusTable, rangeVector, absWoundPos, angleOffset, woundPresetName) + local MOSprite = ToMOSprite(MO); + local holeRadius = math.random(radiusTable[1], radiusTable[2]); + local woundEmitterOffset = Vector(holeRadius, 0):GetRadRotatedCopy(rangeVector.AbsRadAngle + angleOffset); -- Vector to push the created wound in from the new hole + local holeOffset = SceneMan:ShortestDistance(MO.Pos, absWoundPos, true); + local woundOffset = holeOffset + woundEmitterOffset; -- Push the wound MO inwards to make it visually spawn on the MO rather than thin air + local holePos = MOSprite:UnRotateOffset(holeOffset); + local woundPos = MOSprite:UnRotateOffset(woundOffset); + + -- Creates the wound at the default position if the presetname exists; script might bork if no wound is given + local newWound = nil; + if (woundPresetName ~= "") then + newWound = CreateAEmitter(woundPresetName); + local inboundAngle = rangeVector:GetXFlipped(MO.HFlipped).AbsRadAngle; + local woundAngle = inboundAngle - (MO.RotAngle * MO.FlipFactor) + math.pi + angleOffset; -- ... We should probably have an MOSprite:UnRotateAngle() function + -- newWound.Lifetime = 50; + -- newWound.BurstDamage = 0; + MO:AddWound(newWound, woundPos, countTowardsWoundLimit); + newWound.InheritedRotAngleOffset = woundAngle; + end + + -- Makes a hole in the sprite, discolouring the outermost pixels instead of removing them. + -- Iterates radially, could be made into a square with a distance check if coverage is spotty. + for i = 0, holeRadius do + local circumference = holeRadius * 2 * math.pi; + local angleStep = (math.pi*2)/circumference; + for q = 1, circumference do + local pos = Vector(i, 0):GetRadRotatedCopy(angleStep*q).Ceilinged + (holePos - MOSprite.SpriteOffset); + local color = 0; -- Default hole colour is transparent + + -- If we're at the edge of the hole and the wound has any colours, set pixel colour to a random wound colour instead of transparent + if (i == holeRadius and IsMOSprite(newWound)) then + local colorTable = GetAllSpriteColors(ToMOSprite(newWound)); + if (#colorTable > 0) then + color = colorTable[math.random(1, #colorTable)]; + end + end + + -- Change pixel colour on all frames of the sprite and, if we're at the edge, make a table of all valid positions on the outer ring + for frame = 0, MOSprite.FrameCount do + if (MOSprite:SetSpritePixelIndex(pos.X, pos.Y, frame, color, 0, false) and i == holeRadius) then + table.insert(var.ringPositions, pos + MOSprite.SpriteOffset); + end + end + end + end + + -- Attempts to displace all wound MOs within the radius to the edge of it + for wound in MO:GetWounds() do + local woundDist = wound.ParentOffset - holePos; + if (woundDist.Magnitude < holeRadius) then + -- Calculate a vector from hole centre to wound position and set it to equal the radius of the hole, pushing the wound out to the edge + local newDist = Vector(woundDist.X, woundDist.Y); + local newOffset = holePos + newDist:SetMagnitude(holeRadius); + local bitmapOffset = newOffset - MOSprite.SpriteOffset; + -- If the calculated position isn't transparent, set parentoffset to this + if (MOSprite:GetSpritePixelIndex(bitmapOffset.X, bitmapOffset.Y, MOSprite.Frame) == -2) then + wound.ParentOffset = newOffset; + else + -- If calculated position was invalid, pick a random position on the outside ring + if (#var.ringPositions > 0) then + local pos; + local bitmapPos; + local foundPixel = false; + repeat + pos = table.remove(var.ringPositions, math.random(1, #var.ringPositions)); + bitmapPos = pos - MOSprite.SpriteOffset; + foundPixel = MOSprite:GetSpritePixelIndex(bitmapPos.X, bitmapPos.Y, MOSprite.Frame) > 0; + until + #var.ringPositions <= 0 or foundPixel + + if (foundPixel) then + wound.ParentOffset = pos; + else + -- If, somehow, no valid position is found, delete the wound; this might need changing but is an edge case + wound.ToDelete = true; + end + else + -- If there are no outer ring positions, delete the wound + wound.ToDelete = true; + end + end + end + end + + return newWound; +end + +function OnCollideWithMO(self, hitMO, hitMORootParent) + local var = self.var; + + -- Calculate MOSR penetration power + local penetration = self.Mass * var.Sharpness * var.Vel.Magnitude * sharpnessMultiplier; + + -- If the target isn't about to cease existing, the bullet hasn't penetrated this frame and the material of the MO is weak enough to penetrate, proceed + if hitMO.ToDelete == false and var.canPenetrate and hitMO.Material.StructuralIntegrity <= penetration then + var.canPenetrate = false; -- Ensure this is only run once per frame + local rangeVector = var.Vel/3; + local endPos = var.Pos + rangeVector; + + -- We do already have the MO but we need the point of impact + local raycast = SceneMan:CastMORay(var.Pos, rangeVector, self.RootID, self.IgnoresWhichTeam, 0, true, 0); + + if raycast ~= 255 then + endPos = SceneMan:GetLastRayHitPos(); -- Point of impact, woo + local MO = ToMOSRotating(MovableMan:GetMOFromID(raycast)); + local MOSprite = ToMOSprite(MO); + var.ringPositions = {}; -- Reset ring position table for this collision + local maxPen = penetration / MO.Material.StructuralIntegrity; -- Max penetration depth + local penVec = rangeVector.Normalized; + local hitOffset = SceneMan:ShortestDistance(MO.Pos, endPos, true); + + -- Add the entry wound + addDeformWound(var, MO, entryWoundRadius, rangeVector, endPos, 0, MO:GetEntryWoundPresetName()); + + -- Bit of table bullshit for Lua performance; just use vectors in C++ + local startPos = {hitOffset.X, hitOffset.Y}; + local exitWoundPos = nil; + local penVecTable = {penVec.X, penVec.Y}; + local penUsed = 0; + local pixelFound = false; + -- Check for exit wound + for i = 1, maxPen do + local checkPos = Vector(startPos[1] + penVecTable[1]*i, startPos[2] + penVecTable[2]*i); + checkPos = MOSprite:UnRotateOffset(checkPos); + checkPos = checkPos - MOSprite.SpriteOffset; + local pixel = MOSprite:GetSpritePixelIndex(checkPos.X, checkPos.Y, MOSprite.Frame); + + -- If we've found a valid pixel and the iterator exits the visible sprite, add exit wound at last found pixel + if (pixelFound and pixel <= 0) then + exitWoundPos = Vector(startPos[1] + penVecTable[1]*i, startPos[2] + penVecTable[2]*i); + pixelFound = false; + end + + -- If outside of sprite dimensions, break loop + if (pixel < 0) then + break; + end + + -- If we find a visible pixel + if (pixel > 0) then + penUsed = penUsed + MO.Material.StructuralIntegrity; + pixelFound = true; + end + + -- If all penetration has been spent, break loop + if (penUsed >= penetration) then + break; + end + end + + -- If a valid exit wound position has been found, add exit wound and set bullet to appear out of this wound with appropriately reduced velocity + if (exitWoundPos) then + local exitWound = addDeformWound(var, MO, exitWoundRadius, rangeVector, exitWoundPos + MO.Pos, math.pi, MO:GetExitWoundPresetName()); + var.newVel = rangeVector * 3 * (1-(penUsed / penetration)); + var.newPos = exitWoundPos + MO.Pos; + self:SetWhichMOToNotHit(MO:GetRootParent(), 0.035); -- Makes sure the bullet only hits this MOSR once + else + self.ToDelete = true; + var.newVel = (endPos - self.Pos) / 3; -- Attempts to prevent the bullet from visually bouncing off for one frame + end + end + end +end + +function Update(self) + local var = self.var; + var.canPenetrate = true; + + -- We have to set new velocities and positions in Update because it borks in OnCollideWithMO + if (var.newVel) then + self.Vel = Vector(var.newVel.X, var.newVel.Y); + var.newVel = nil; + end + + if (var.newPos) then + self.Pos = Vector(var.newPos.X, var.newPos.Y); + var.newPos = nil; + end +end \ No newline at end of file