diff --git a/CHANGELOG.md b/CHANGELOG.md index 167485eed8..814ed247f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,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 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, "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. + + 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. - New `MovableMan` function `GetMOsAtPosition(posX, posY, ignoreTeam, getsHitByMOsOnly)` that will return an iterator with all the `MovableObject`s that intersect that exact position with their sprite. @@ -161,6 +177,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/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 diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index efd9fdd72c..ef43a25f82 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(); @@ -346,9 +353,90 @@ 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); +} + +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() { diff --git a/Source/Entities/MOSprite.h b/Source/Entities/MOSprite.h index 9c84ee79da..d4b2715f2c 100644 --- a/Source/Entities/MOSprite.h +++ b/Source/Entities/MOSprite.h @@ -90,6 +90,48 @@ 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 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 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* 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* 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. int GetSpriteWidth() const { return m_aSprite[0] ? m_aSprite[0]->w : 0; } @@ -342,6 +384,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/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 dc148bfeae..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,6 +838,12 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MOSprite) { .def("SetExitWound", &MOSprite::SetExitWound) .def("GetEntryWoundPresetName", &MOSprite::GetEntryWoundPresetName) .def("GetExitWoundPresetName", &MOSprite::GetExitWoundPresetName) + .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),