<commit_before>//-----------------------------------------------
//
//	This file is part of the HamFramework for Siv3D.
//
//	Copyright (C) 2014-2017 HAMSTRO
//
//	Licensed under the MIT License.
//
//-----------------------------------------------

# pragma once
# include <Siv3D.hpp>

namespace s3d
{
	class BasicCamera2D
	{
	protected:

		Vec2 m_pos = Window::BaseSize() * 0.5;

		double m_scale = 1.0;

	public:

		BasicCamera2D() = default;

		constexpr BasicCamera2D(const Vec2& pos, double scale) noexcept
			: m_pos(pos)
			, m_scale(scale) {}

		virtual ~BasicCamera2D() = default;

		void setPos(const Vec2& pos) noexcept
		{
			m_pos = pos;
		}

		const Vec2& getPos() const noexcept
		{
			return m_pos;
		}

		void setScale(double scale) noexcept
		{
			m_scale = scale;
		}

		double getScale() const noexcept
		{
			return m_scale;
		}

		RectF getCameraArea() const
		{
			return RectF(m_pos - Window::BaseSize() * 0.5 / m_scale, Window::BaseSize() / m_scale);
		}

		Mat3x2 getMat3x2() const
		{
			return Mat3x2::Scale(m_scale).translate(Window::BaseSize() * 0.5 - m_scale * m_pos);
		}

		Transformer2D createTransformer() const
		{
			return Transformer2D(getMat3x2(), true);
		}
	};

	class Camera2D : public BasicCamera2D
	{
	private:

		void controlWithKeyboard(double speed, double scaleRatio)
		{
			if (m_keyConfig[0].pressed())
			{
				m_targetPos.y -= (speed / m_targetScale);
			}

			if (m_keyConfig[1].pressed())
			{
				m_targetPos.x -= (speed / m_targetScale);
			}

			if (m_keyConfig[2].pressed())
			{
				m_targetPos.y += (speed / m_targetScale);
			}

			if (m_keyConfig[3].pressed())
			{
				m_targetPos.x += (speed / m_targetScale);
			}

			if (m_keyConfig[4].pressed())
			{
				m_targetScale *= scaleRatio;
			}

			if (m_keyConfig[5].pressed())
			{
				m_targetScale /= scaleRatio;
			}
		}

		void controlWithMouse(bool useMouseControl, double speed, double scaleRatio, double mouseSpeedRatio)
		{
			if (!useMouseControl)
			{
				m_grabPos = none;

				return;
			}

			{
				const Transformer2D transformer(Mat3x2::Identity(), Cursor::GetTransform().inversed());

				if (MouseR.down())
				{
					m_grabPos = Cursor::Pos();
				}
				else if (m_grabPos)
				{
					const Point delta = Cursor::Pos() - m_grabPos.value();

					m_targetPos += mouseSpeedRatio * speed * delta / m_targetScale;

					if (MouseR.up())
					{
						m_grabPos = none;
					}
				}
			}

			const double wheel = Mouse::Wheel();

			if (wheel < 0.0)
			{
				m_targetScale *= scaleRatio;
			}
			else if (wheel > 0.0)
			{
				m_targetScale /= scaleRatio;
			}
		}

	protected:

		Vec2 m_targetPos = BasicCamera2D::m_pos;

		double m_targetScale = BasicCamera2D::m_scale;

		std::array<Key, 6> m_keyConfig =
		{
			{ KeyW, KeyA, KeyS, KeyD, KeyUp, KeyDown }
		};

		Optional<Point> m_grabPos;

	public:

		Camera2D() = default;

		Camera2D(const Vec2& pos, double scale)
			: BasicCamera2D(pos, scale)
			, m_targetPos(pos)
			, m_targetScale(scale)
		{

		}

		virtual ~Camera2D() {}

		void update(double speed = 10.0, double scaleRatio = 1.1, double mouseSpeedRatio = 0.01, double lerpRatio = 0.2, bool useKeyControl = true, bool useMouseControl = true)
		{
			if (useKeyControl)
			{
				controlWithKeyboard(speed, scaleRatio);
			}

			if (useMouseControl)
			{
				controlWithMouse(useMouseControl, speed, scaleRatio, mouseSpeedRatio);
			}

			m_pos = Math::Lerp(m_pos, m_targetPos, lerpRatio);

			m_scale = Math::Lerp(m_scale, m_targetScale, lerpRatio);
		}

		void draw(const ColorF& color = Palette::White) const
		{
			if (!m_grabPos)
			{
				return;
			}

			const Transformer2D transformer(Graphics2D::GetTransform().inversed(), true);

			const double radius = 12.0;
			const Point delta = Cursor::Pos() - m_grabPos.value();
			const double length = delta.length();

			Circle(m_grabPos.value(), radius).drawFrame(2.0, 2.0, color);

			if (length >= radius * 2)
			{
				const Vec2 direction = delta / length;
				const Vec2 normalDirection{ direction.y, -direction.x };
				const Vec2 leftOffset = normalDirection * 10;

				const Vec2 p0 = delta;
				const Vec2 p1 = Vec2(direction).setLength(radius * 1.2) - leftOffset;
				const Vec2 p2 = Vec2(direction).setLength(radius * 1.8);
				const Vec2 p3 = Vec2(direction).setLength(radius * 1.2) + leftOffset;

				Quad(p1, p2, p3, p0).moveBy(m_grabPos.value()).draw(color);
			}
		}

		void setKeyConfig(const std::array<Key, 6>& keys)
		{
			m_keyConfig = keys;
		}

		void setTargetPos(const Vec2& targetPos)
		{
			m_targetPos = targetPos;
		}

		void setTargetScale(const double targetScale)
		{
			m_targetScale = targetScale;
		}
	};
}

/* example

*/
<commit_msg>Update Camera2D.hpp<commit_after>//-----------------------------------------------
//
//	This file is part of the HamFramework for Siv3D.
//
//	Copyright (C) 2014-2017 HAMSTRO
//
//	Licensed under the MIT License.
//
//-----------------------------------------------

# pragma once
# include <Siv3D.hpp>

namespace s3d
{
	class BasicCamera2D
	{
	protected:

		Vec2 m_pos = Window::BaseSize() * 0.5;

		double m_scale = 1.0;

	public:

		BasicCamera2D() = default;

		constexpr BasicCamera2D(const Vec2& pos, double scale) noexcept
			: m_pos(pos)
			, m_scale(scale) {}

		virtual ~BasicCamera2D() = default;

		void setPos(const Vec2& pos) noexcept
		{
			m_pos = pos;
		}

		const Vec2& getPos() const noexcept
		{
			return m_pos;
		}

		void setScale(double scale) noexcept
		{
			m_scale = scale;
		}

		double getScale() const noexcept
		{
			return m_scale;
		}

		RectF getCameraArea() const
		{
			return RectF(m_pos - Window::BaseSize() * 0.5 / m_scale, Window::BaseSize() / m_scale);
		}

		Mat3x2 getMat3x2() const
		{
			return Mat3x2::Scale(m_scale).translated(Window::BaseSize() * 0.5 - m_scale * m_pos);
		}

		Transformer2D createTransformer() const
		{
			return Transformer2D(getMat3x2(), true);
		}
	};

	class Camera2D : public BasicCamera2D
	{
	private:

		bool m_keyControlEnabled = true;

		bool m_mouseControlEnabled = true;

		double m_speed = 10.0;

		double m_scaleRatio = 1.1;

		double m_mouseSpeedRatio = 0.01;

		double m_lerpRatio = 0.2;

		void controlWithKeyboard()
		{
			if (!m_keyControlEnabled)
			{
				return;
			}

			if (m_controls[0]())
			{
				m_targetPos.y -= (m_speed / m_targetScale);
			}

			if (m_controls[1]())
			{
				m_targetPos.x -= (m_speed / m_targetScale);
			}

			if (m_controls[2]())
			{
				m_targetPos.y += (m_speed / m_targetScale);
			}

			if (m_controls[3]())
			{
				m_targetPos.x += (m_speed / m_targetScale);
			}

			if (m_controls[4]())
			{
				m_targetScale *= m_scaleRatio;
			}

			if (m_controls[5]())
			{
				m_targetScale /= m_scaleRatio;
			}
		}

		void controlWithMouse()
		{
			if (!m_mouseControlEnabled)
			{
				m_grabPos = none;

				return;
			}

			{
				const Transformer2D transformer(Mat3x2::Identity(), Cursor::GetTransform().inversed());

				if (MouseR.down())
				{
					m_grabPos = Cursor::Pos();
				}
				else if (m_grabPos)
				{
					const Point delta = Cursor::Pos() - m_grabPos.value();

					m_targetPos += m_mouseSpeedRatio * m_speed * delta / m_targetScale;

					if (MouseR.up())
					{
						m_grabPos = none;
					}
				}
			}

			const double wheel = Mouse::Wheel();

			if (wheel < 0.0)
			{
				m_targetScale *= m_scaleRatio;
			}
			else if (wheel > 0.0)
			{
				m_targetScale /= m_scaleRatio;
			}
		}

	protected:

		Vec2 m_targetPos = BasicCamera2D::m_pos;

		double m_targetScale = BasicCamera2D::m_scale;

		std::array<std::function<bool()>, 6> m_controls =
		{{ [](){ return KeyW.pressed(); }
		,  [](){ return KeyA.pressed(); }
		,  [](){ return KeyS.pressed(); }
		,  [](){ return KeyD.pressed(); }
		,  [](){ return KeyUp.pressed(); }
		,  [](){ return KeyDown.pressed(); }}};

		Optional<Point> m_grabPos;

	public:

		Camera2D() = default;

		Camera2D(const Vec2& pos, double scale, bool keyControlEnabled = true, bool mouseControlEnabled = true, double speed = 10.0, double scaleRatio = 1.1, double lerpRatio = 0.2)
			: BasicCamera2D(pos, scale)
			, m_targetPos(pos)
			, m_targetScale(scale)
			, m_keyControlEnabled(keyControlEnabled)
			, m_mouseControlEnabled(mouseControlEnabled)
			, m_speed(speed)
			, m_scaleRatio(scaleRatio)
			, m_lerpRatio(lerpRatio)
		{

		}

		virtual ~Camera2D() = default;

		void update()
		{
			controlWithKeyboard();

			controlWithMouse();

			m_pos = Math::Lerp(m_pos, m_targetPos, m_lerpRatio);

			m_scale = Math::Lerp(m_scale, m_targetScale, m_lerpRatio);
		}

		void draw(const ColorF& color = Palette::White) const
		{
			if (!m_grabPos)
			{
				return;
			}

			const Transformer2D transformer(Graphics2D::GetTransform().inversed(), true);

			const double radius = 12.0;
			const Point delta = Cursor::Pos() - m_grabPos.value();
			const double length = delta.length();

			Circle(m_grabPos.value(), radius).drawFrame(2.0, 2.0, color);

			if (length >= radius * 2)
			{
				const Vec2 direction = delta / length;
				const Vec2 normalDirection{ direction.y, -direction.x };
				const Vec2 leftOffset = normalDirection * 10;

				const Vec2 p0 = delta;
				const Vec2 p1 = Vec2(direction).setLength(radius * 1.2) - leftOffset;
				const Vec2 p2 = Vec2(direction).setLength(radius * 1.8);
				const Vec2 p3 = Vec2(direction).setLength(radius * 1.2) + leftOffset;

				Quad(p1, p2, p3, p0).moveBy(m_grabPos.value()).draw(color);
			}
		}

		void setControls(const std::array<std::function<bool()>, 6>& controls)
		{
			m_controls = controls;
		}

		void setTargetPos(const Vec2& targetPos) noexcept
		{
			m_targetPos = targetPos;
		}

		void setTargetScale(double targetScale) noexcept
		{
			m_targetScale = targetScale;
		}

		void setSpeed(double speed) noexcept
		{
			m_speed = speed;
		}

		double getSpeed() const noexcept
		{
			return m_speed;
		}

		void setScaleRatio(double scaleRatio) noexcept
		{
			m_scaleRatio = scaleRatio;
		}

		double getScaleRatio() const noexcept
		{
			return m_scaleRatio;
		}

		void setMouseSpeedRatio(double mouseSpeedRatio) noexcept
		{
			m_mouseSpeedRatio = mouseSpeedRatio;
		}

		double getMouseSpeedRatio() const noexcept
		{
			return m_mouseSpeedRatio;
		}

		void setLerpRatio(double lerpRatio) noexcept
		{
			m_lerpRatio = lerpRatio;
		}

		double getLerpRatio() const noexcept
		{
			return m_lerpRatio;
		}

		void enableKeyControl(bool enable) noexcept
		{
			m_keyControlEnabled = enable;
		}

		bool keyControlEnabled() const noexcept
		{
			return m_keyControlEnabled;
		}

		void enableMouseControl(bool enable) noexcept
		{
			m_mouseControlEnabled = enable;
		}

		bool mouseControlEnabled() const noexcept
		{
			return m_mouseControlEnabled;
		}
	};
}

/* example

*/
<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* KeynoteImportFilter: Sets up the filter, and calls OdpExporter
 * to do the actual filtering
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <boost/shared_ptr.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/ucb/XContent.hpp>
#include <com/sun/star/xml/sax/XAttributeList.hpp>
#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/XParser.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/uno/Reference.h>
#include <comphelper/processfactory.hxx>
#include <comphelper/types.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <iostream>
#include <libetonyek/libetonyek.h>
#include <libodfgen/libodfgen.hxx>
#include <osl/diagnose.h>
#include <rtl/tencinfo.h>
#include <ucbhelper/content.hxx>

#include <writerperfect/DirectoryStream.hxx>
#include <writerperfect/DocumentHandler.hxx>
#include <writerperfect/WPXSvInputStream.hxx>

#include <xmloff/attrlist.hxx>

#include "KeynoteImportFilter.hxx"

using boost::shared_ptr;

using namespace ::com::sun::star::uno;
using com::sun::star::uno::Reference;
using com::sun::star::io::XInputStream;
using com::sun::star::io::XSeekable;
using com::sun::star::uno::Sequence;
using com::sun::star::uno::Any;
using com::sun::star::uno::UNO_QUERY;
using com::sun::star::uno::XInterface;
using com::sun::star::uno::Exception;
using com::sun::star::uno::RuntimeException;
using com::sun::star::beans::PropertyValue;
using com::sun::star::document::XFilter;
using com::sun::star::document::XExtendedFilterDetection;
using com::sun::star::document::XImporter;
using com::sun::star::xml::sax::InputSource;
using com::sun::star::xml::sax::XAttributeList;
using com::sun::star::xml::sax::XDocumentHandler;
using com::sun::star::xml::sax::XParser;

using writerperfect::DocumentHandler;
using writerperfect::WPXSvInputStream;

namespace beans = com::sun::star::beans;
namespace container = com::sun::star::container;
namespace ucb = com::sun::star::ucb;

namespace
{

template<class T>
bool lcl_queryIsPackage( const Sequence<T> &lComponentData )
{
    bool bIsPackage = false;

    const sal_Int32 nLength = lComponentData.getLength();
    const T *pValue = lComponentData.getConstArray();
    for ( sal_Int32 i = 0; i < nLength; ++i)
    {
        if ( pValue[i].Name == "IsPackage" )
        {
            pValue[i].Value >>= bIsPackage;
            break;
        }
    }

    return bIsPackage;
}

bool lcl_isPackage( const Any &rComponentData )
{
    Sequence < beans::NamedValue > lComponentDataNV;
    Sequence < beans::PropertyValue > lComponentDataPV;

    if ( rComponentData >>= lComponentDataNV )
        return lcl_queryIsPackage( lComponentDataNV );
    else if ( rComponentData >>= lComponentDataPV )
        return lcl_queryIsPackage( lComponentDataPV );

    return false;
}
}

sal_Bool SAL_CALL KeynoteImportFilter::filter( const Sequence< ::com::sun::star::beans::PropertyValue >& aDescriptor )
throw (RuntimeException, std::exception)
{
    sal_Int32 nLength = aDescriptor.getLength();
    const PropertyValue *pValue = aDescriptor.getConstArray();
    Reference < XInputStream > xInputStream;
    Reference < ucb::XContent > xContent;
    bool bIsPackage = false;
    for ( sal_Int32 i = 0 ; i < nLength; i++)
    {
        if ( pValue[i].Name == "ComponentData" )
            bIsPackage = lcl_isPackage( pValue[i].Value );
        else if ( pValue[i].Name == "InputStream" )
            pValue[i].Value >>= xInputStream;
        else if ( pValue[i].Name == "UCBContent" )
            pValue[i].Value >>= xContent;
    }
    if ( !xInputStream.is() )
    {
        OSL_ASSERT( false );
        return sal_False;
    }

    if ( bIsPackage && !xContent.is() )
    {
        SAL_WARN("writerperfect", "the input claims to be a package, but does not have UCBContent");
        bIsPackage = false;
    }

    // An XML import service: what we push sax messages to..
    Reference < XDocumentHandler > xInternalHandler(
        mxContext->getServiceManager()->createInstanceWithContext(
            "com.sun.star.comp.Draw.XMLOasisImporter", mxContext),
        css::uno::UNO_QUERY_THROW);

    // The XImporter sets up an empty target document for XDocumentHandler to write to..
    Reference < XImporter > xImporter(xInternalHandler, UNO_QUERY);
    xImporter->setTargetDocument( mxDoc );

    // OO Graphics Handler: abstract class to handle document SAX messages, concrete implementation here
    // writes to in-memory target doc
    DocumentHandler xHandler(xInternalHandler);

    shared_ptr< librevenge::RVNGInputStream > input;
    if ( bIsPackage )
        input.reset( new writerperfect::DirectoryStream( xContent ) );
    else
        input.reset( new WPXSvInputStream( xInputStream ) );

    OdpGenerator exporter;
    exporter.addDocumentHandler(&xHandler, ODF_FLAT_XML);
    bool tmpParseResult = libetonyek::EtonyekDocument::parse(input.get(), &exporter);
    return tmpParseResult;
}

void SAL_CALL KeynoteImportFilter::cancel(  )
throw (RuntimeException, std::exception)
{
}

// XImporter
void SAL_CALL KeynoteImportFilter::setTargetDocument( const Reference< ::com::sun::star::lang::XComponent >& xDoc )
throw (::com::sun::star::lang::IllegalArgumentException, RuntimeException, std::exception)
{
    mxDoc = xDoc;
}

// XExtendedFilterDetection
OUString SAL_CALL KeynoteImportFilter::detect( com::sun::star::uno::Sequence< PropertyValue >& Descriptor )
throw( com::sun::star::uno::RuntimeException, std::exception )
{
    sal_Int32 nLength = Descriptor.getLength();
    sal_Int32 nNewLength = nLength + 2;
    sal_Int32 nComponentDataLocation = -1;
    sal_Int32 nTypeNameLocation = -1;
    sal_Int32 nUCBContentLocation = -1;
    bool bIsPackage = false;
    bool bUCBContentChanged = false;
    const PropertyValue *pValue = Descriptor.getConstArray();
    Reference < XInputStream > xInputStream;
    Reference < ucb::XContent > xContent;
    Sequence < beans::NamedValue > lComponentDataNV;
    Sequence < beans::PropertyValue > lComponentDataPV;
    bool bComponentDataNV = true;

    for ( sal_Int32 i = 0 ; i < nLength; i++)
    {
        if ( pValue[i].Name == "TypeName" )
        {
            nTypeNameLocation = i;
            --nNewLength;
        }
        if ( pValue[i].Name == "ComponentData" )
        {
            bComponentDataNV = pValue[i].Value >>= lComponentDataNV;
            if (!bComponentDataNV)
                pValue[i].Value >>= lComponentDataPV;
            nComponentDataLocation = i;
            --nNewLength;
        }
        else if ( pValue[i].Name == "InputStream" )
        {
            pValue[i].Value >>= xInputStream;
        }
        else if ( pValue[i].Name == "UCBContent" )
        {
            pValue[i].Value >>= xContent;
            nUCBContentLocation = i;
        }
    }

    assert(nNewLength >= nLength);

    if (!xInputStream.is())
        return OUString();

    shared_ptr< librevenge::RVNGInputStream > input( new WPXSvInputStream( xInputStream ) );

    /* Apple Keynote documents come in two variants:
     * * actual files (zip), only produced by Keynote 5 (at least with
     *   default settings)
     * * packages (IOW, directories), produced by Keynote 1-4 and again
     *   starting with 6.
     * But since the libetonyek import only works with a stream, we need
     * to pass it one for the whole package. Here we determine if that
     * is needed.
     *
     * Note: for convenience, we also recognize that the main XML file
     * from a package was passed and pass the whole package to the
     * filter instead.
     */
    if ( xContent.is() )
    {
        ucbhelper::Content aContent( xContent, Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
        try
        {
            if ( aContent.isFolder() )
            {
                input.reset( new writerperfect::DirectoryStream( xContent ) );
                bIsPackage = true;
            }
        }
        catch (...)
        {
            return OUString();
        }

        libetonyek::EtonyekDocument::Type type = libetonyek::EtonyekDocument::TYPE_UNKNOWN;
        const libetonyek::EtonyekDocument::Confidence confidence = libetonyek::EtonyekDocument::isSupported( input.get(), &type );
        if ((libetonyek::EtonyekDocument::CONFIDENCE_NONE == confidence) || (libetonyek::EtonyekDocument::TYPE_KEYNOTE != type))
            return OUString();

        if ( confidence == libetonyek::EtonyekDocument::CONFIDENCE_SUPPORTED_PART )
        {
            assert( !bIsPackage );

            const Reference < container::XChild > xChild( xContent, UNO_QUERY );
            if ( xChild.is() )
            {
                const Reference < ucb::XContent > xPackageContent( xChild->getParent(), UNO_QUERY );
                if ( xPackageContent.is() )
                {
                    input.reset( new writerperfect::DirectoryStream( xPackageContent ) );
                    if ( libetonyek::EtonyekDocument::CONFIDENCE_EXCELLENT == libetonyek::EtonyekDocument::isSupported( input.get() ) )
                    {
                        xContent = xPackageContent;
                        bUCBContentChanged = true;
                        bIsPackage = true;
                    }
                }
            }
        }
    }

    // we do not need to insert ComponentData if this is not a package
    if ( !bIsPackage && ( nComponentDataLocation == -1 ) )
        --nNewLength;

    if ( nNewLength > nLength )
        Descriptor.realloc( nNewLength );

    if ( nTypeNameLocation == -1 )
    {
        assert( nLength < nNewLength );
        nTypeNameLocation = nLength++;
        Descriptor[nTypeNameLocation].Name = "TypeName";
    }

    if ( bIsPackage && ( nComponentDataLocation == -1 ) )
    {
        assert( nLength < nNewLength );
        nComponentDataLocation = nLength++;
        Descriptor[nComponentDataLocation].Name = "ComponentData";
    }

    if ( bIsPackage )
    {
        if (bComponentDataNV)
        {
            const sal_Int32 nCDSize = lComponentDataNV.getLength();
            lComponentDataNV.realloc( nCDSize + 1 );
            beans::NamedValue aValue;
            aValue.Name = "IsPackage";
            aValue.Value = comphelper::makeBoolAny(true);
            lComponentDataNV[nCDSize] = aValue;
            Descriptor[nComponentDataLocation].Value <<= lComponentDataNV;
        }
        else
        {
            const sal_Int32 nCDSize = lComponentDataPV.getLength();
            lComponentDataPV.realloc( nCDSize + 1 );
            beans::PropertyValue aProp;
            aProp.Name = "IsPackage";
            aProp.Value = comphelper::makeBoolAny(true);
            aProp.Handle = -1;
            aProp.State = beans::PropertyState_DIRECT_VALUE;
            lComponentDataPV[nCDSize] = aProp;
            Descriptor[nComponentDataLocation].Value <<= lComponentDataPV;
        }
    }

    if ( bUCBContentChanged )
        Descriptor[nUCBContentLocation].Value <<= xContent;

    const OUString sTypeName("impress_AppleKeynote");
    Descriptor[nTypeNameLocation].Value <<= sTypeName;

    return sTypeName;
}

// XInitialization
void SAL_CALL KeynoteImportFilter::initialize( const Sequence< Any >& aArguments )
throw (Exception, RuntimeException, std::exception)
{
    Sequence < PropertyValue > aAnySeq;
    sal_Int32 nLength = aArguments.getLength();
    if ( nLength && ( aArguments[0] >>= aAnySeq ) )
    {
        const PropertyValue *pValue = aAnySeq.getConstArray();
        nLength = aAnySeq.getLength();
        for ( sal_Int32 i = 0 ; i < nLength; i++)
        {
            if ( pValue[i].Name == "Type" )
            {
                pValue[i].Value >>= msFilterName;
                break;
            }
        }
    }
}

OUString KeynoteImportFilter_getImplementationName ()
throw (RuntimeException)
{
    return OUString ( "org.libreoffice.comp.Impress.KeynoteImportFilter" );
}

#define SERVICE_NAME1 "com.sun.star.document.ImportFilter"
#define SERVICE_NAME2 "com.sun.star.document.ExtendedTypeDetection"

Sequence< OUString > SAL_CALL KeynoteImportFilter_getSupportedServiceNames(  )
throw (RuntimeException)
{
    Sequence < OUString > aRet(2);
    OUString *pArray = aRet.getArray();
    pArray[0] =  OUString ( SERVICE_NAME1 );
    pArray[1] =  OUString ( SERVICE_NAME2 );
    return aRet;
}

#undef SERVICE_NAME2
#undef SERVICE_NAME1

Reference< XInterface > SAL_CALL KeynoteImportFilter_createInstance( const Reference< XComponentContext > & rContext)
throw( Exception )
{
    return (cppu::OWeakObject *) new KeynoteImportFilter( rContext );
}

// XServiceInfo
OUString SAL_CALL KeynoteImportFilter::getImplementationName(  )
throw (RuntimeException, std::exception)
{
    return KeynoteImportFilter_getImplementationName();
}

sal_Bool SAL_CALL KeynoteImportFilter::supportsService( const OUString &rServiceName )
throw (RuntimeException, std::exception)
{
    return cppu::supportsService(this, rServiceName);
}

Sequence< OUString > SAL_CALL KeynoteImportFilter::getSupportedServiceNames(  )
throw (RuntimeException, std::exception)
{
    return KeynoteImportFilter_getSupportedServiceNames();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<commit_msg>use the right XML import service<commit_after>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* KeynoteImportFilter: Sets up the filter, and calls OdpExporter
 * to do the actual filtering
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <boost/shared_ptr.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/ucb/XContent.hpp>
#include <com/sun/star/xml/sax/XAttributeList.hpp>
#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/XParser.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/uno/Reference.h>
#include <comphelper/processfactory.hxx>
#include <comphelper/types.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <iostream>
#include <libetonyek/libetonyek.h>
#include <libodfgen/libodfgen.hxx>
#include <osl/diagnose.h>
#include <rtl/tencinfo.h>
#include <ucbhelper/content.hxx>

#include <writerperfect/DirectoryStream.hxx>
#include <writerperfect/DocumentHandler.hxx>
#include <writerperfect/WPXSvInputStream.hxx>

#include <xmloff/attrlist.hxx>

#include "KeynoteImportFilter.hxx"

using boost::shared_ptr;

using namespace ::com::sun::star::uno;
using com::sun::star::uno::Reference;
using com::sun::star::io::XInputStream;
using com::sun::star::io::XSeekable;
using com::sun::star::uno::Sequence;
using com::sun::star::uno::Any;
using com::sun::star::uno::UNO_QUERY;
using com::sun::star::uno::XInterface;
using com::sun::star::uno::Exception;
using com::sun::star::uno::RuntimeException;
using com::sun::star::beans::PropertyValue;
using com::sun::star::document::XFilter;
using com::sun::star::document::XExtendedFilterDetection;
using com::sun::star::document::XImporter;
using com::sun::star::xml::sax::InputSource;
using com::sun::star::xml::sax::XAttributeList;
using com::sun::star::xml::sax::XDocumentHandler;
using com::sun::star::xml::sax::XParser;

using writerperfect::DocumentHandler;
using writerperfect::WPXSvInputStream;

namespace beans = com::sun::star::beans;
namespace container = com::sun::star::container;
namespace ucb = com::sun::star::ucb;

namespace
{

template<class T>
bool lcl_queryIsPackage( const Sequence<T> &lComponentData )
{
    bool bIsPackage = false;

    const sal_Int32 nLength = lComponentData.getLength();
    const T *pValue = lComponentData.getConstArray();
    for ( sal_Int32 i = 0; i < nLength; ++i)
    {
        if ( pValue[i].Name == "IsPackage" )
        {
            pValue[i].Value >>= bIsPackage;
            break;
        }
    }

    return bIsPackage;
}

bool lcl_isPackage( const Any &rComponentData )
{
    Sequence < beans::NamedValue > lComponentDataNV;
    Sequence < beans::PropertyValue > lComponentDataPV;

    if ( rComponentData >>= lComponentDataNV )
        return lcl_queryIsPackage( lComponentDataNV );
    else if ( rComponentData >>= lComponentDataPV )
        return lcl_queryIsPackage( lComponentDataPV );

    return false;
}
}

sal_Bool SAL_CALL KeynoteImportFilter::filter( const Sequence< ::com::sun::star::beans::PropertyValue >& aDescriptor )
throw (RuntimeException, std::exception)
{
    sal_Int32 nLength = aDescriptor.getLength();
    const PropertyValue *pValue = aDescriptor.getConstArray();
    Reference < XInputStream > xInputStream;
    Reference < ucb::XContent > xContent;
    bool bIsPackage = false;
    for ( sal_Int32 i = 0 ; i < nLength; i++)
    {
        if ( pValue[i].Name == "ComponentData" )
            bIsPackage = lcl_isPackage( pValue[i].Value );
        else if ( pValue[i].Name == "InputStream" )
            pValue[i].Value >>= xInputStream;
        else if ( pValue[i].Name == "UCBContent" )
            pValue[i].Value >>= xContent;
    }
    if ( !xInputStream.is() )
    {
        OSL_ASSERT( false );
        return sal_False;
    }

    if ( bIsPackage && !xContent.is() )
    {
        SAL_WARN("writerperfect", "the input claims to be a package, but does not have UCBContent");
        bIsPackage = false;
    }

    // An XML import service: what we push sax messages to..
    Reference < XDocumentHandler > xInternalHandler(
        mxContext->getServiceManager()->createInstanceWithContext(
            "com.sun.star.comp.Impress.XMLOasisImporter", mxContext),
        css::uno::UNO_QUERY_THROW);

    // The XImporter sets up an empty target document for XDocumentHandler to write to..
    Reference < XImporter > xImporter(xInternalHandler, UNO_QUERY);
    xImporter->setTargetDocument( mxDoc );

    // OO Graphics Handler: abstract class to handle document SAX messages, concrete implementation here
    // writes to in-memory target doc
    DocumentHandler xHandler(xInternalHandler);

    shared_ptr< librevenge::RVNGInputStream > input;
    if ( bIsPackage )
        input.reset( new writerperfect::DirectoryStream( xContent ) );
    else
        input.reset( new WPXSvInputStream( xInputStream ) );

    OdpGenerator exporter;
    exporter.addDocumentHandler(&xHandler, ODF_FLAT_XML);
    bool tmpParseResult = libetonyek::EtonyekDocument::parse(input.get(), &exporter);
    return tmpParseResult;
}

void SAL_CALL KeynoteImportFilter::cancel(  )
throw (RuntimeException, std::exception)
{
}

// XImporter
void SAL_CALL KeynoteImportFilter::setTargetDocument( const Reference< ::com::sun::star::lang::XComponent >& xDoc )
throw (::com::sun::star::lang::IllegalArgumentException, RuntimeException, std::exception)
{
    mxDoc = xDoc;
}

// XExtendedFilterDetection
OUString SAL_CALL KeynoteImportFilter::detect( com::sun::star::uno::Sequence< PropertyValue >& Descriptor )
throw( com::sun::star::uno::RuntimeException, std::exception )
{
    sal_Int32 nLength = Descriptor.getLength();
    sal_Int32 nNewLength = nLength + 2;
    sal_Int32 nComponentDataLocation = -1;
    sal_Int32 nTypeNameLocation = -1;
    sal_Int32 nUCBContentLocation = -1;
    bool bIsPackage = false;
    bool bUCBContentChanged = false;
    const PropertyValue *pValue = Descriptor.getConstArray();
    Reference < XInputStream > xInputStream;
    Reference < ucb::XContent > xContent;
    Sequence < beans::NamedValue > lComponentDataNV;
    Sequence < beans::PropertyValue > lComponentDataPV;
    bool bComponentDataNV = true;

    for ( sal_Int32 i = 0 ; i < nLength; i++)
    {
        if ( pValue[i].Name == "TypeName" )
        {
            nTypeNameLocation = i;
            --nNewLength;
        }
        if ( pValue[i].Name == "ComponentData" )
        {
            bComponentDataNV = pValue[i].Value >>= lComponentDataNV;
            if (!bComponentDataNV)
                pValue[i].Value >>= lComponentDataPV;
            nComponentDataLocation = i;
            --nNewLength;
        }
        else if ( pValue[i].Name == "InputStream" )
        {
            pValue[i].Value >>= xInputStream;
        }
        else if ( pValue[i].Name == "UCBContent" )
        {
            pValue[i].Value >>= xContent;
            nUCBContentLocation = i;
        }
    }

    assert(nNewLength >= nLength);

    if (!xInputStream.is())
        return OUString();

    shared_ptr< librevenge::RVNGInputStream > input( new WPXSvInputStream( xInputStream ) );

    /* Apple Keynote documents come in two variants:
     * * actual files (zip), only produced by Keynote 5 (at least with
     *   default settings)
     * * packages (IOW, directories), produced by Keynote 1-4 and again
     *   starting with 6.
     * But since the libetonyek import only works with a stream, we need
     * to pass it one for the whole package. Here we determine if that
     * is needed.
     *
     * Note: for convenience, we also recognize that the main XML file
     * from a package was passed and pass the whole package to the
     * filter instead.
     */
    if ( xContent.is() )
    {
        ucbhelper::Content aContent( xContent, Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
        try
        {
            if ( aContent.isFolder() )
            {
                input.reset( new writerperfect::DirectoryStream( xContent ) );
                bIsPackage = true;
            }
        }
        catch (...)
        {
            return OUString();
        }

        libetonyek::EtonyekDocument::Type type = libetonyek::EtonyekDocument::TYPE_UNKNOWN;
        const libetonyek::EtonyekDocument::Confidence confidence = libetonyek::EtonyekDocument::isSupported( input.get(), &type );
        if ((libetonyek::EtonyekDocument::CONFIDENCE_NONE == confidence) || (libetonyek::EtonyekDocument::TYPE_KEYNOTE != type))
            return OUString();

        if ( confidence == libetonyek::EtonyekDocument::CONFIDENCE_SUPPORTED_PART )
        {
            assert( !bIsPackage );

            const Reference < container::XChild > xChild( xContent, UNO_QUERY );
            if ( xChild.is() )
            {
                const Reference < ucb::XContent > xPackageContent( xChild->getParent(), UNO_QUERY );
                if ( xPackageContent.is() )
                {
                    input.reset( new writerperfect::DirectoryStream( xPackageContent ) );
                    if ( libetonyek::EtonyekDocument::CONFIDENCE_EXCELLENT == libetonyek::EtonyekDocument::isSupported( input.get() ) )
                    {
                        xContent = xPackageContent;
                        bUCBContentChanged = true;
                        bIsPackage = true;
                    }
                }
            }
        }
    }

    // we do not need to insert ComponentData if this is not a package
    if ( !bIsPackage && ( nComponentDataLocation == -1 ) )
        --nNewLength;

    if ( nNewLength > nLength )
        Descriptor.realloc( nNewLength );

    if ( nTypeNameLocation == -1 )
    {
        assert( nLength < nNewLength );
        nTypeNameLocation = nLength++;
        Descriptor[nTypeNameLocation].Name = "TypeName";
    }

    if ( bIsPackage && ( nComponentDataLocation == -1 ) )
    {
        assert( nLength < nNewLength );
        nComponentDataLocation = nLength++;
        Descriptor[nComponentDataLocation].Name = "ComponentData";
    }

    if ( bIsPackage )
    {
        if (bComponentDataNV)
        {
            const sal_Int32 nCDSize = lComponentDataNV.getLength();
            lComponentDataNV.realloc( nCDSize + 1 );
            beans::NamedValue aValue;
            aValue.Name = "IsPackage";
            aValue.Value = comphelper::makeBoolAny(true);
            lComponentDataNV[nCDSize] = aValue;
            Descriptor[nComponentDataLocation].Value <<= lComponentDataNV;
        }
        else
        {
            const sal_Int32 nCDSize = lComponentDataPV.getLength();
            lComponentDataPV.realloc( nCDSize + 1 );
            beans::PropertyValue aProp;
            aProp.Name = "IsPackage";
            aProp.Value = comphelper::makeBoolAny(true);
            aProp.Handle = -1;
            aProp.State = beans::PropertyState_DIRECT_VALUE;
            lComponentDataPV[nCDSize] = aProp;
            Descriptor[nComponentDataLocation].Value <<= lComponentDataPV;
        }
    }

    if ( bUCBContentChanged )
        Descriptor[nUCBContentLocation].Value <<= xContent;

    const OUString sTypeName("impress_AppleKeynote");
    Descriptor[nTypeNameLocation].Value <<= sTypeName;

    return sTypeName;
}

// XInitialization
void SAL_CALL KeynoteImportFilter::initialize( const Sequence< Any >& aArguments )
throw (Exception, RuntimeException, std::exception)
{
    Sequence < PropertyValue > aAnySeq;
    sal_Int32 nLength = aArguments.getLength();
    if ( nLength && ( aArguments[0] >>= aAnySeq ) )
    {
        const PropertyValue *pValue = aAnySeq.getConstArray();
        nLength = aAnySeq.getLength();
        for ( sal_Int32 i = 0 ; i < nLength; i++)
        {
            if ( pValue[i].Name == "Type" )
            {
                pValue[i].Value >>= msFilterName;
                break;
            }
        }
    }
}

OUString KeynoteImportFilter_getImplementationName ()
throw (RuntimeException)
{
    return OUString ( "org.libreoffice.comp.Impress.KeynoteImportFilter" );
}

#define SERVICE_NAME1 "com.sun.star.document.ImportFilter"
#define SERVICE_NAME2 "com.sun.star.document.ExtendedTypeDetection"

Sequence< OUString > SAL_CALL KeynoteImportFilter_getSupportedServiceNames(  )
throw (RuntimeException)
{
    Sequence < OUString > aRet(2);
    OUString *pArray = aRet.getArray();
    pArray[0] =  OUString ( SERVICE_NAME1 );
    pArray[1] =  OUString ( SERVICE_NAME2 );
    return aRet;
}

#undef SERVICE_NAME2
#undef SERVICE_NAME1

Reference< XInterface > SAL_CALL KeynoteImportFilter_createInstance( const Reference< XComponentContext > & rContext)
throw( Exception )
{
    return (cppu::OWeakObject *) new KeynoteImportFilter( rContext );
}

// XServiceInfo
OUString SAL_CALL KeynoteImportFilter::getImplementationName(  )
throw (RuntimeException, std::exception)
{
    return KeynoteImportFilter_getImplementationName();
}

sal_Bool SAL_CALL KeynoteImportFilter::supportsService( const OUString &rServiceName )
throw (RuntimeException, std::exception)
{
    return cppu::supportsService(this, rServiceName);
}

Sequence< OUString > SAL_CALL KeynoteImportFilter::getSupportedServiceNames(  )
throw (RuntimeException, std::exception)
{
    return KeynoteImportFilter_getSupportedServiceNames();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: XMLErrorIndicatorPropertyHdl.cxx,v $
 *
 *  $Revision: 1.5 $
 *
 *  last change: $Author: hr $ $Date: 2007-06-27 14:51:25 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_xmloff.hxx"
#include "XMLErrorIndicatorPropertyHdl.hxx"

#ifndef _XMLOFF_XMLUCONV_HXX
#include <xmloff/xmluconv.hxx>
#endif
#ifndef _COM_SUN_STAR_CHART_CHARTERRORINDICATORTYPE_HPP_
#include <com/sun/star/chart/ChartErrorIndicatorType.hpp>
#endif
#ifndef _RTL_USTRBUF_HXX_
#include <rtl/ustrbuf.hxx>
#endif

using namespace com::sun::star;

XMLErrorIndicatorPropertyHdl::~XMLErrorIndicatorPropertyHdl()
{}

sal_Bool XMLErrorIndicatorPropertyHdl::importXML( const ::rtl::OUString& rStrImpValue,
                                                  uno::Any& rValue, const SvXMLUnitConverter& /*rUnitConverter*/ ) const
{
    sal_Bool bValue;
    SvXMLUnitConverter::convertBool( bValue, rStrImpValue );

    // modify existing value
    chart::ChartErrorIndicatorType eType = chart::ChartErrorIndicatorType_NONE;
    if( rValue.hasValue())
        rValue >>= eType;

    if( bValue )    // enable flag
    {
        if( eType != chart::ChartErrorIndicatorType_TOP_AND_BOTTOM )
        {
            if( mbUpperIndicator )
                eType = ( eType == chart::ChartErrorIndicatorType_LOWER )
                    ? chart::ChartErrorIndicatorType_TOP_AND_BOTTOM
                    : chart::ChartErrorIndicatorType_UPPER;
            else
                eType = ( eType == chart::ChartErrorIndicatorType_UPPER )
                    ? chart::ChartErrorIndicatorType_TOP_AND_BOTTOM
                    : chart::ChartErrorIndicatorType_LOWER;
        }
    }
    else            // disable flag
    {
        if( eType != chart::ChartErrorIndicatorType_NONE )
        {
            if( mbUpperIndicator )
                eType = ( eType == chart::ChartErrorIndicatorType_UPPER )
                    ? chart::ChartErrorIndicatorType_NONE
                    : chart::ChartErrorIndicatorType_LOWER;
            else
                eType = ( eType == chart::ChartErrorIndicatorType_LOWER )
                    ? chart::ChartErrorIndicatorType_NONE
                    : chart::ChartErrorIndicatorType_UPPER;
        }
    }

    rValue <<= eType;

    return sal_True;
}

sal_Bool XMLErrorIndicatorPropertyHdl::exportXML( ::rtl::OUString& rStrExpValue,
                                                  const uno::Any& rValue, const SvXMLUnitConverter& /*rUnitConverter*/ ) const
{
    rtl::OUStringBuffer aBuffer;
    chart::ChartErrorIndicatorType eType;

    rValue >>= eType;
    sal_Bool bValue = ( eType == chart::ChartErrorIndicatorType_TOP_AND_BOTTOM ||
                        ( mbUpperIndicator
                          ? ( eType == chart::ChartErrorIndicatorType_UPPER )
                          : ( eType == chart::ChartErrorIndicatorType_LOWER )));

    if( bValue )
    {
        SvXMLUnitConverter::convertBool( aBuffer, bValue );
        rStrExpValue = aBuffer.makeStringAndClear();
    }

    // only export if set to true
    return bValue;
}
<commit_msg>INTEGRATION: CWS changefileheader (1.5.162); FILE MERGED 2008/04/01 16:09:32 thb 1.5.162.3: #i85898# Stripping all external header guards 2008/04/01 13:04:33 thb 1.5.162.2: #i85898# Stripping all external header guards 2008/03/31 16:28:01 rt 1.5.162.1: #i87441# Change license header to LPGL v3.<commit_after>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: XMLErrorIndicatorPropertyHdl.cxx,v $
 * $Revision: 1.6 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_xmloff.hxx"
#include "XMLErrorIndicatorPropertyHdl.hxx"
#include <xmloff/xmluconv.hxx>
#include <com/sun/star/chart/ChartErrorIndicatorType.hpp>
#include <rtl/ustrbuf.hxx>

using namespace com::sun::star;

XMLErrorIndicatorPropertyHdl::~XMLErrorIndicatorPropertyHdl()
{}

sal_Bool XMLErrorIndicatorPropertyHdl::importXML( const ::rtl::OUString& rStrImpValue,
                                                  uno::Any& rValue, const SvXMLUnitConverter& /*rUnitConverter*/ ) const
{
    sal_Bool bValue;
    SvXMLUnitConverter::convertBool( bValue, rStrImpValue );

    // modify existing value
    chart::ChartErrorIndicatorType eType = chart::ChartErrorIndicatorType_NONE;
    if( rValue.hasValue())
        rValue >>= eType;

    if( bValue )    // enable flag
    {
        if( eType != chart::ChartErrorIndicatorType_TOP_AND_BOTTOM )
        {
            if( mbUpperIndicator )
                eType = ( eType == chart::ChartErrorIndicatorType_LOWER )
                    ? chart::ChartErrorIndicatorType_TOP_AND_BOTTOM
                    : chart::ChartErrorIndicatorType_UPPER;
            else
                eType = ( eType == chart::ChartErrorIndicatorType_UPPER )
                    ? chart::ChartErrorIndicatorType_TOP_AND_BOTTOM
                    : chart::ChartErrorIndicatorType_LOWER;
        }
    }
    else            // disable flag
    {
        if( eType != chart::ChartErrorIndicatorType_NONE )
        {
            if( mbUpperIndicator )
                eType = ( eType == chart::ChartErrorIndicatorType_UPPER )
                    ? chart::ChartErrorIndicatorType_NONE
                    : chart::ChartErrorIndicatorType_LOWER;
            else
                eType = ( eType == chart::ChartErrorIndicatorType_LOWER )
                    ? chart::ChartErrorIndicatorType_NONE
                    : chart::ChartErrorIndicatorType_UPPER;
        }
    }

    rValue <<= eType;

    return sal_True;
}

sal_Bool XMLErrorIndicatorPropertyHdl::exportXML( ::rtl::OUString& rStrExpValue,
                                                  const uno::Any& rValue, const SvXMLUnitConverter& /*rUnitConverter*/ ) const
{
    rtl::OUStringBuffer aBuffer;
    chart::ChartErrorIndicatorType eType;

    rValue >>= eType;
    sal_Bool bValue = ( eType == chart::ChartErrorIndicatorType_TOP_AND_BOTTOM ||
                        ( mbUpperIndicator
                          ? ( eType == chart::ChartErrorIndicatorType_UPPER )
                          : ( eType == chart::ChartErrorIndicatorType_LOWER )));

    if( bValue )
    {
        SvXMLUnitConverter::convertBool( aBuffer, bValue );
        rStrExpValue = aBuffer.makeStringAndClear();
    }

    // only export if set to true
    return bValue;
}
<|endoftext|>


<commit_before>/*
 * TcpIpAsyncClientSsl.hpp
 *
 * Copyright (C) 2020 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#ifndef CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
#define CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP

#include <boost/scoped_ptr.hpp>

#include <boost/asio/ip/tcp.hpp>

#include "BoostAsioSsl.hpp"

#include <core/http/AsyncClient.hpp>
#include <core/http/TcpIpAsyncConnector.hpp>

#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#include <cryptuiapi.h>
#include <openssl/x509.h>
#endif

#ifdef __APPLE__
#include <core/http/Keychain.hpp>
#include <openssl/x509.h>
#endif

namespace rstudio {
namespace core {
namespace http {

class TcpIpAsyncClientSsl
   : public AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >
{
public:
   TcpIpAsyncClientSsl(boost::asio::io_service& ioService,
                       const std::string& address,
                       const std::string& port,
                       bool verify,
                       const std::string& certificateAuthority = std::string(),
                       const boost::posix_time::time_duration& connectionTimeout =
                          boost::posix_time::time_duration(boost::posix_time::pos_infin),
                       const std::string& hostname = std::string() )
     : AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >(ioService),
       sslContext_(boost::asio::ssl::context::sslv23_client),
       address_(address),
       port_(port),
       verify_(verify),
       certificateAuthority_(certificateAuthority),
       connectionTimeout_(connectionTimeout)
   {
      if (verify_)
      {
         sslContext_.set_default_verify_paths();
         sslContext_.set_verify_mode(boost::asio::ssl::context::verify_peer);

         if (!certificateAuthority_.empty())
         {
            boost::asio::const_buffer buff(certificateAuthority_.data(), certificateAuthority_.size());
            boost::system::error_code ec;
            sslContext_.add_certificate_authority(buff, ec);
            if (ec)
               LOG_ERROR(Error(ec, ERROR_LOCATION));
         }

      #ifdef _WIN32
         // on Windows, OpenSSL does not support loading certificates from the Windows certificate store
         // because of this, each time we need to verify certificates, we initialize
         // all certificates individually with OpenSSL
         const ICertStore& certStore = getCertificateStore();
      #endif

      #ifdef __APPLE__
         // on OSX, OpenSSL does not support loading certificates from the Keychain
         // because of this, each time we need to verify certificates, we initialize
         // all certificates individually with OpenSSL, similar to what is done above for Windows
         const ICertStore& certStore = getKeychain();
      #endif

      #if defined(__APPLE__) || defined(_WIN32)
         for (const auto& cert : certStore.getCertificates())
         {
            if (X509_STORE* store = SSL_CTX_get_cert_store(sslContext_.native_handle()))
            {
               if (::X509_STORE_add_cert(store, cert) != 1)
               {
                  char* subjectName = X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0);
                  std::string subjectNameStr(subjectName);
                  OPENSSL_free(subjectName);

                  boost::system::error_code ec = boost::system::error_code(
                              static_cast<int>(::ERR_get_error()),
                              boost::asio::error::get_ssl_category());
                  Error error(ec, ERROR_LOCATION);
                  error.addProperty("Description", "Could not add Windows certificate to OpenSSL cert store: " + subjectNameStr);

                  LOG_ERROR(error);
               }
            }
         }
      #endif
      }
      else
      {
         sslContext_.set_verify_mode(boost::asio::ssl::context::verify_none);
      }

      // use scoped ptr so we can call the constructor after we've configured
      // the ssl::context (immediately above)
      ptrSslStream_.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(ioService, sslContext_));

      // TLS v1.3 requires that the SNI be set, so set the SNI.
      if (!SSL_set_tlsext_host_name(
            ptrSslStream_->native_handle(),
            (hostname.empty() ? address_.c_str() : hostname.c_str())))
      {
         boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
         LOG_ERROR(Error(ec, ERROR_LOCATION));
      }
   }

protected:

   virtual boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& socket()
   {
      return *(ptrSslStream_);
   }

   virtual void connectAndWriteRequest()
   {
      boost::shared_ptr<TcpIpAsyncConnector> pAsyncConnector(
                  new TcpIpAsyncConnector(ioService(),
                                          &(ptrSslStream_->next_layer())));

      pAsyncConnector->connect(
            address_,
            port_,
            boost::bind(&TcpIpAsyncClientSsl::performHandshake,
                        TcpIpAsyncClientSsl::sharedFromThis()),
            boost::bind(&TcpIpAsyncClientSsl::handleConnectionError,
                        TcpIpAsyncClientSsl::sharedFromThis(),
                        _1),
            connectionTimeout_);
   }

   virtual std::string getDefaultHostHeader()
   {
      return address_ + ":" + port_;
   }

private:

   void performHandshake()
   {
      if (verify_)
      {
         ptrSslStream_->set_verify_callback(
                            boost::asio::ssl::rfc2818_verification(address_));
      }
      ptrSslStream_->async_handshake(
            boost::asio::ssl::stream_base::client,
            boost::bind(&TcpIpAsyncClientSsl::handleHandshake,
                        sharedFromThis(),
                        boost::asio::placeholders::error));
   }

   void handleHandshake(const boost::system::error_code& ec)
   {
      try
      {
         if (!ec)
         {
            // finished handshake, commence with request
            writeRequest();
         }
         else
         {
            handleErrorCode(ec, ERROR_LOCATION);
         }
      }
      CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
   }

   const boost::shared_ptr<TcpIpAsyncClientSsl> sharedFromThis()
   {
      boost::shared_ptr<AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > > ptrShared
                                                 = shared_from_this();

      return boost::static_pointer_cast<TcpIpAsyncClientSsl>(ptrShared);
   }

   virtual bool isShutdownError(const boost::system::error_code& ec)
   {
      return util::isSslShutdownError(ec);
   }

private:
   boost::asio::ssl::context sslContext_;
   boost::scoped_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > ptrSslStream_;
   std::string address_;
   std::string port_;
   bool verify_;
   std::string certificateAuthority_;
   boost::posix_time::time_duration connectionTimeout_;

#if defined(_WIN32) || defined(__APPLE__)
   class ICertStore
   {
   public:
      virtual std::vector<X509*> getCertificates() const = 0;
      virtual ~ICertStore() {}
   };
#endif

#ifdef _WIN32
   class WindowsCertificateStore : public ICertStore
   {
   public:
      WindowsCertificateStore()
      {
         // load certificates from important stores
         LPCSTR stores[] = {"ROOT", "CA"};
         for (const LPCSTR& store : stores)
         {
             HCERTSTORE hStore = CertOpenSystemStore(NULL, store);
             if (!hStore)
             {
                LOG_ERROR_MESSAGE("Could not open certificate store");
                return;
             }

             PCCERT_CONTEXT pContext = nullptr;
             while (pContext = CertEnumCertificatesInStore(hStore, pContext))
             {
                // convert the certificate returned from the Windows store into a
                // format that OpenSSL can understand
                const BYTE* certPtr = pContext->pbCertEncoded;
                X509* x509 = d2i_X509(nullptr, &certPtr, pContext->cbCertEncoded);
                if (x509)
                   certificates.push_back(x509);
             }

             CertCloseStore(hStore, 0);
         }
      }

      std::vector<X509*> getCertificates() const
      {
         return certificates;
      }

   private:
      // certificate pointers - these are intentionally leaked
      // as they need to be available for the entire run of the program
      std::vector<X509*> certificates;
   };

   static const ICertStore& getCertificateStore()
   {
       // Meyer's singleton - guarantees this is thread safe
       // and will be initialized exactly once by the first caller
       static WindowsCertificateStore instance;
       return instance;
   }
#endif

#ifdef __APPLE__
   class Keychain : public ICertStore
   {
   public:
      Keychain()
      {
         // load all certs from the keychain
         std::vector<KeychainCertificateData> certs = getKeychainCertificates();
         for (const auto& cert : certs)
         {
            // convert the raw bytes from the keychain into a format that OpenSSL can understand
            const unsigned char* bytePtr = cert.data.get();
            X509* x509 = d2i_X509(nullptr, &bytePtr, cert.size);
            if (x509)
               certificates.push_back(x509);
         }
      }

      std::vector<X509*> getCertificates() const
      {
         return certificates;
      }

   private:
      // certificate pointers - these are intentionally leaked
      // as they need to be available for the entire run of the program
      std::vector<X509*> certificates;
   };

   static const ICertStore& getKeychain()
   {
      // Meyer's singleton - guarantees this is thread safe
      // and will be initialized exactly once by the first caller
      static Keychain instance;
      return instance;
   }
#endif
};

} // namespace http
} // namespace core
} // namespace rstudio

#endif // CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
<commit_msg>Code review feedback - log inability to parse X509 certificate (as debug to prevent potential spam), and notate that returned certificates should not be freed.<commit_after>/*
 * TcpIpAsyncClientSsl.hpp
 *
 * Copyright (C) 2020 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#ifndef CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
#define CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP

#include <boost/scoped_ptr.hpp>

#include <boost/asio/ip/tcp.hpp>

#include "BoostAsioSsl.hpp"

#include <core/http/AsyncClient.hpp>
#include <core/http/TcpIpAsyncConnector.hpp>

#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#include <cryptuiapi.h>
#include <openssl/x509.h>
#endif

#ifdef __APPLE__
#include <core/http/Keychain.hpp>
#include <openssl/x509.h>
#endif

namespace rstudio {
namespace core {
namespace http {

class TcpIpAsyncClientSsl
   : public AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >
{
public:
   TcpIpAsyncClientSsl(boost::asio::io_service& ioService,
                       const std::string& address,
                       const std::string& port,
                       bool verify,
                       const std::string& certificateAuthority = std::string(),
                       const boost::posix_time::time_duration& connectionTimeout =
                          boost::posix_time::time_duration(boost::posix_time::pos_infin),
                       const std::string& hostname = std::string() )
     : AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >(ioService),
       sslContext_(boost::asio::ssl::context::sslv23_client),
       address_(address),
       port_(port),
       verify_(verify),
       certificateAuthority_(certificateAuthority),
       connectionTimeout_(connectionTimeout)
   {
      if (verify_)
      {
         sslContext_.set_default_verify_paths();
         sslContext_.set_verify_mode(boost::asio::ssl::context::verify_peer);

         if (!certificateAuthority_.empty())
         {
            boost::asio::const_buffer buff(certificateAuthority_.data(), certificateAuthority_.size());
            boost::system::error_code ec;
            sslContext_.add_certificate_authority(buff, ec);
            if (ec)
               LOG_ERROR(Error(ec, ERROR_LOCATION));
         }

      #ifdef _WIN32
         // on Windows, OpenSSL does not support loading certificates from the Windows certificate store
         // because of this, each time we need to verify certificates, we initialize
         // all certificates individually with OpenSSL
         const ICertStore& certStore = getCertificateStore();
      #endif

      #ifdef __APPLE__
         // on OSX, OpenSSL does not support loading certificates from the Keychain
         // because of this, each time we need to verify certificates, we initialize
         // all certificates individually with OpenSSL, similar to what is done above for Windows
         const ICertStore& certStore = getKeychain();
      #endif

      #if defined(__APPLE__) || defined(_WIN32)
         for (const auto& cert : certStore.getCertificates())
         {
            if (X509_STORE* store = SSL_CTX_get_cert_store(sslContext_.native_handle()))
            {
               if (::X509_STORE_add_cert(store, cert) != 1)
               {
                  char* subjectName = X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0);
                  std::string subjectNameStr(subjectName);
                  OPENSSL_free(subjectName);

                  boost::system::error_code ec = boost::system::error_code(
                              static_cast<int>(::ERR_get_error()),
                              boost::asio::error::get_ssl_category());
                  Error error(ec, ERROR_LOCATION);
                  error.addProperty("Description", "Could not add Windows certificate to OpenSSL cert store: " + subjectNameStr);

                  LOG_ERROR(error);
               }
            }
         }
      #endif
      }
      else
      {
         sslContext_.set_verify_mode(boost::asio::ssl::context::verify_none);
      }

      // use scoped ptr so we can call the constructor after we've configured
      // the ssl::context (immediately above)
      ptrSslStream_.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(ioService, sslContext_));

      // TLS v1.3 requires that the SNI be set, so set the SNI.
      if (!SSL_set_tlsext_host_name(
            ptrSslStream_->native_handle(),
            (hostname.empty() ? address_.c_str() : hostname.c_str())))
      {
         boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
         LOG_ERROR(Error(ec, ERROR_LOCATION));
      }
   }

protected:

   virtual boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& socket()
   {
      return *(ptrSslStream_);
   }

   virtual void connectAndWriteRequest()
   {
      boost::shared_ptr<TcpIpAsyncConnector> pAsyncConnector(
                  new TcpIpAsyncConnector(ioService(),
                                          &(ptrSslStream_->next_layer())));

      pAsyncConnector->connect(
            address_,
            port_,
            boost::bind(&TcpIpAsyncClientSsl::performHandshake,
                        TcpIpAsyncClientSsl::sharedFromThis()),
            boost::bind(&TcpIpAsyncClientSsl::handleConnectionError,
                        TcpIpAsyncClientSsl::sharedFromThis(),
                        _1),
            connectionTimeout_);
   }

   virtual std::string getDefaultHostHeader()
   {
      return address_ + ":" + port_;
   }

private:

   void performHandshake()
   {
      if (verify_)
      {
         ptrSslStream_->set_verify_callback(
                            boost::asio::ssl::rfc2818_verification(address_));
      }
      ptrSslStream_->async_handshake(
            boost::asio::ssl::stream_base::client,
            boost::bind(&TcpIpAsyncClientSsl::handleHandshake,
                        sharedFromThis(),
                        boost::asio::placeholders::error));
   }

   void handleHandshake(const boost::system::error_code& ec)
   {
      try
      {
         if (!ec)
         {
            // finished handshake, commence with request
            writeRequest();
         }
         else
         {
            handleErrorCode(ec, ERROR_LOCATION);
         }
      }
      CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
   }

   const boost::shared_ptr<TcpIpAsyncClientSsl> sharedFromThis()
   {
      boost::shared_ptr<AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > > ptrShared
                                                 = shared_from_this();

      return boost::static_pointer_cast<TcpIpAsyncClientSsl>(ptrShared);
   }

   virtual bool isShutdownError(const boost::system::error_code& ec)
   {
      return util::isSslShutdownError(ec);
   }

private:
   boost::asio::ssl::context sslContext_;
   boost::scoped_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > ptrSslStream_;
   std::string address_;
   std::string port_;
   bool verify_;
   std::string certificateAuthority_;
   boost::posix_time::time_duration connectionTimeout_;

#if defined(_WIN32) || defined(__APPLE__)
   class ICertStore
   {
   public:
      virtual std::vector<X509*> getCertificates() const = 0;
      virtual ~ICertStore() {}
   };
#endif

#ifdef _WIN32
   class WindowsCertificateStore : public ICertStore
   {
   public:
      WindowsCertificateStore()
      {
         // load certificates from important stores
         LPCSTR stores[] = {"ROOT", "CA"};
         for (const LPCSTR& store : stores)
         {
             HCERTSTORE hStore = CertOpenSystemStore(NULL, store);
             if (!hStore)
             {
                LOG_ERROR_MESSAGE("Could not open certificate store");
                return;
             }

             PCCERT_CONTEXT pContext = nullptr;
             while (pContext = CertEnumCertificatesInStore(hStore, pContext))
             {
                // convert the certificate returned from the Windows store into a
                // format that OpenSSL can understand
                const BYTE* certPtr = pContext->pbCertEncoded;
                X509* x509 = d2i_X509(nullptr, &certPtr, pContext->cbCertEncoded);
                if (x509)
                {
                   certificates.push_back(x509);
                }
                else
                {
                   boost::system::error_code ec = boost::system::error_code(
                               static_cast<int>(::ERR_get_error()),
                               boost::asio::error::get_ssl_category());
                   Error error(ec, ERROR_LOCATION);
                   error.addProperty("Description",
                                     "Could not create OpenSSL certificate object from Windows certificate data");
                   LOG_DEBUG_MESSAGE(error.asString());
                }
             }

             CertCloseStore(hStore, 0);
         }
      }

      // note to caller - the certificate data (X509 pointers) here must not be freed as the data
      // must persist for the lifetime of the process
      std::vector<X509*> getCertificates() const
      {
         return certificates;
      }

   private:
      // certificate pointers - these are intentionally leaked
      // as they need to be available for the entire run of the program
      std::vector<X509*> certificates;
   };

   static const ICertStore& getCertificateStore()
   {
       // Meyer's singleton - guarantees this is thread safe
       // and will be initialized exactly once by the first caller
       static WindowsCertificateStore instance;
       return instance;
   }
#endif

#ifdef __APPLE__
   class Keychain : public ICertStore
   {
   public:
      Keychain()
      {
         // load all certs from the keychain
         std::vector<KeychainCertificateData> certs = getKeychainCertificates();
         for (const auto& cert : certs)
         {
            // convert the raw bytes from the keychain into a format that OpenSSL can understand
            const unsigned char* bytePtr = cert.data.get();
            X509* x509 = d2i_X509(nullptr, &bytePtr, cert.size);
            if (x509)
            {
               certificates.push_back(x509);
            }
            else
            {
               boost::system::error_code ec = boost::system::error_code(
                           static_cast<int>(::ERR_get_error()),
                           boost::asio::error::get_ssl_category());
               Error error(ec, ERROR_LOCATION);
               error.addProperty("Description",
                                 "Could not create OpenSSL certificate object from Keychain certificate data");
               LOG_DEBUG_MESSAGE(error.asString());
            }
         }
      }

      // note to caller - the certificate data (X509 pointers) here must not be freed as the data
      // must persist for the lifetime of the process
      std::vector<X509*> getCertificates() const
      {
         return certificates;
      }

   private:
      // certificate pointers - these are intentionally leaked
      // as they need to be available for the entire run of the program
      std::vector<X509*> certificates;
   };

   static const ICertStore& getKeychain()
   {
      // Meyer's singleton - guarantees this is thread safe
      // and will be initialized exactly once by the first caller
      static Keychain instance;
      return instance;
   }
#endif
};

} // namespace http
} // namespace core
} // namespace rstudio

#endif // CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
<|endoftext|>


<commit_before>// Fill out your copyright notice in the Description page of Project Settings.

#include "CubeWars.h"
#include "DestroyableObstacle.h"
#include "CubeDebris.h"
#include "ObstacleMovementComponent.h"
#include "Net/UnrealNetwork.h"
#include "CubeWarsGameMode.h"

namespace {
	const FVector CUBE_DIMENSIONS(20.0f, 20.0f, 20.0f);
	const int32 NUM_PARTS_PER_COLUMN = 4;
}

// Sets default values
ADestroyableObstacle::ADestroyableObstacle()
	: MinConstructionTime(0.5f)
	, MaxConstructionTime(1.0f)
	, MinConstructionDistanceMultiplier(2.0f)
	, MaxConstructionDistanceMultiplier(3.0f)
	, NumPartColumns(5)
	, Dimensions(20.0f, 300.0f, 50.0f)
	, ExplosionForce(5000.0f)
	, MaxHealth(26.0f)
	, Health(MaxHealth)
	, TotalConstructionTime(MaxConstructionTime)
	, ConstructionTimer(0.0f)
	, FinalPartMaterial(nullptr)
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Basic setup
	bReplicates = true;
	bReplicateMovement = true;

	BaseCollisionComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootComponent"));
	RootComponent = BaseCollisionComponent;
	//BaseCollisionComponent->InitBoxExtent(FVector(10.0f));
	BaseCollisionComponent->SetBoxExtent(Dimensions / 2.0f, true);
	BaseCollisionComponent->SetCollisionProfileName(TEXT("Obstacle"));
	BaseCollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	BaseCollisionComponent->SetSimulatePhysics(false);

	// Add the visual representations
	//The mesh is 20x20x20 -> match the parts to be the size of Dimensions in total
	const FVector DIMENSION_RATIO = Dimensions / CUBE_DIMENSIONS;
	const float CENTER_WIDTH = Dimensions.Y - (Dimensions.Y / NumPartColumns);
	const float X_OFFSET = Dimensions.X / 4.0f;
	const float Z_OFFSET = Dimensions.Z / 4.0f;

	FVector PartScale(DIMENSION_RATIO.X / 2.0f, (Dimensions.Y / NumPartColumns) / CUBE_DIMENSIONS.Y, DIMENSION_RATIO.Z / 2.0f);

	{
		// Create and position a mesh component so we can see where our cube is
		static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/Meshes/SimpleCube.SimpleCube"));
		if (CubeVisualAsset.Succeeded())
		{
			static ConstructorHelpers::FObjectFinder<UMaterialInstance> Material(TEXT("/Game/Materials/QuadraticTransparent.QuadraticTransparent"));

			if (Material.Succeeded())
			{
				for (int32 i = 0; i < NumPartColumns; ++i)
				{
					const float CENTER_Y = CENTER_WIDTH * (i / static_cast<float>(NumPartColumns - 1)) - CENTER_WIDTH / 2.0f;

					TArray<UStaticMeshComponent*> NewVisuals;

					NewVisuals.Add(CreateObstaclePart(FVector(X_OFFSET, CENTER_Y, Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN));
					NewVisuals.Add(CreateObstaclePart(FVector(X_OFFSET, CENTER_Y, -Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 1));
					NewVisuals.Add(CreateObstaclePart(FVector(-X_OFFSET, CENTER_Y, -Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 2));
					NewVisuals.Add(CreateObstaclePart(FVector(-X_OFFSET, CENTER_Y, Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 3));

					FRandomStream RandStream;
					RandStream.GenerateNewSeed();

					// First determine the parts that can fall off
					int32 Index = RandStream.RandRange(0, 3);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					Index = RandStream.RandRange(0, 2);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					Index = RandStream.RandRange(0, 1);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					// Now the fixed parts
					FixedParts.Append(NewVisuals);
				}
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Materials/QuadraticTransparent.QuadraticTransparent'!"));
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Meshes/SimpleCube.SimpleCube'!"));
		}
	}

	// Load the material FinalPartMaterial
	static ConstructorHelpers::FObjectFinder<UMaterialInstance> Material(TEXT("/Game/Materials/Quadratic.Quadratic"));
	if (Material.Succeeded())
	{
		FinalPartMaterial = Material.Object;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Materials/Quadratic.Quadratic'!"));
	}

	// Movement component
	MovementComponent = CreateDefaultSubobject<UObstacleMovementComponent>(TEXT("ObstacleMovement"));
	MovementComponent->UpdatedComponent = RootComponent;
}

// Called when the game starts or when spawned
void ADestroyableObstacle::BeginPlay()
{
	Super::BeginPlay();

	TotalConstructionTime = MaxConstructionTime;

	// Initialize the construction structs for the parts of the obstacle
	FRandomStream Rand;
	for (int32 i = 0; i < ConstructionProgresses.Num(); ++i)
	{
		PartConstructionProgress& PartProgress = ConstructionProgresses[i];
		PartProgress.StartPosition = PartProgress.FinalPosition * Rand.FRandRange(MinConstructionDistanceMultiplier, MaxConstructionDistanceMultiplier);
		PartProgress.TotalTime = Rand.FRandRange(MinConstructionTime, MaxConstructionTime);

		UMaterialInstanceDynamic* MaterialInstance = PartProgress.CubeVisual->CreateDynamicMaterialInstance(0);
		PartProgress.CubeVisual->SetMaterial(0, MaterialInstance);
		PartProgress.MaterialInstance = MaterialInstance;
		MaterialInstance->SetScalarParameterValue("Opacity", 0.0f);
	}
}

// Called every frame
void ADestroyableObstacle::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	for (int32 i = 0; i < ConstructionProgresses.Num();)
	{
		PartConstructionProgress& PartProgress = ConstructionProgresses[i];
		PartProgress.CurrentTime += DeltaTime;

		UStaticMeshComponent* CubeVisual = PartProgress.CubeVisual;
		UMaterialInstanceDynamic* MaterialInstance = PartProgress.MaterialInstance;
		FVector NewPosition;
		float NewOpacity;

		if (PartProgress.CurrentTime >= PartProgress.TotalTime)
		{
			NewPosition = PartProgress.FinalPosition;
			NewOpacity = 1.0f;
			ConstructionProgresses.RemoveAt(i);

			if (FinalPartMaterial)
			{
				CubeVisual->SetMaterial(0, FinalPartMaterial);
			}
		}
		else
		{
			float Delta = PartProgress.CurrentTime / PartProgress.TotalTime;
			NewPosition = FMath::Lerp(PartProgress.StartPosition, PartProgress.FinalPosition, Delta);
			NewOpacity = Delta;
			++i;
		}

		CubeVisual->SetRelativeLocation(NewPosition);

		if (MaterialInstance)
		{
			MaterialInstance->SetScalarParameterValue("Opacity", NewOpacity);
		}

	}
}

void ADestroyableObstacle::Destroyed()
{
	ACubeWarsGameMode* cwgm = GetWorld()->GetAuthGameMode<ACubeWarsGameMode>();
	if (cwgm)
	{
		cwgm->ObstacleDied(ObstacleIndex);
	}
}

float ADestroyableObstacle::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
	Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	if (IsConstructionInProgress())
	{
		if (Health <= 0)
		{
			return 0;
		}

		Health -= DamageAmount;

		if (Health <= 0)
		{
			// Let all the parts fly around and despawn the actor
			ClientFallApart();

			Destroy();
		}
		else
		{
			// Let some parts fly around
			ClientDamageCallback(Health);
		}

		return DamageAmount;
	}

	return 0.0f;
}

UStaticMeshComponent* ADestroyableObstacle::CreateObstaclePart(const FVector& RelativePosition, const FVector& Scale, UStaticMesh* StaticMesh, UMaterialInstance* MatInstance, int32 Number)
{
	FString VisualName("VisualRepresentation");
	VisualName.AppendInt(Number);

	UStaticMeshComponent* CubeVisual = CreateDefaultSubobject<UStaticMeshComponent>(FName(*VisualName));
	CubeVisual->AttachTo(RootComponent);
	CubeVisual->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	CubeVisual->SetCollisionProfileName(TEXT("Obstacle"));
	CubeVisual->SetSimulatePhysics(false);
	CubeVisual->SetRelativeLocation(RelativePosition);
	CubeVisual->SetStaticMesh(StaticMesh);
	CubeVisual->SetWorldScale3D(Scale);
	CubeVisual->SetMaterial(0, MatInstance);

	// Initialize the struct for the construction of th obstacle (some values (FinalPosition and TotalTime) will be recalculated, in BeginPlay, when the values from the blueprint are known)
	PartConstructionProgress PartConstruction(RelativePosition, RelativePosition, CubeVisual, nullptr, 1.0f);
	ConstructionProgresses.Add(PartConstruction);

	return CubeVisual;
}

void ADestroyableObstacle::ClientDamageCallback_Implementation(float NewHealth)
{
	if (!IsRunningDedicatedServer())
	{
		const int32 NUM_TOTAL_FALLOFF_PARTS = ((NumPartColumns * NUM_PARTS_PER_COLUMN) * 3) / 4;
		int32 RemainingParts = static_cast<int32>(NUM_TOTAL_FALLOFF_PARTS * (NewHealth / MaxHealth));

		int32 NumPartsToFallOff = FMath::Max(0, FallOffParts.Num() - RemainingParts);

		FRandomStream RandStream;
		RandStream.GenerateNewSeed();

		for (int32 i = 0; i < NumPartsToFallOff; ++i)
		{
			int32 Index = RandStream.RandRange(0, FallOffParts.Num() - 1);

			UStaticMeshComponent* PartVisual = FallOffParts[Index];
			FallOffParts.RemoveAt(Index);

			CreateDebris(PartVisual);
		}
	}
}

void ADestroyableObstacle::ClientFallApart_Implementation()
{
	for (int32 i = 0; i < FallOffParts.Num(); ++i)
	{
		UStaticMeshComponent* PartVisual = FallOffParts[i];
		CreateDebris(PartVisual);
	}
	FallOffParts.Empty();

	for (int32 i = 0; i < FixedParts.Num(); ++i)
	{
		UStaticMeshComponent* PartVisual = FixedParts[i];
		CreateDebris(PartVisual);
	}
	FixedParts.Empty();
}

void ADestroyableObstacle::CreateDebris(UStaticMeshComponent* PartVisual)
{
	// Destroy the component...
	UWorld* const World = GetWorld();
	PartVisual->DestroyComponent();

	// ...and spawn the actors instead
	FVector Impulse;
	const FVector& ActorLocation = GetActorLocation();
	FVector Location(ActorLocation.X + PartVisual->RelativeLocation.X, ActorLocation.Y + PartVisual->RelativeLocation.Y, ActorLocation.Z + PartVisual->RelativeLocation.Z);
	FActorSpawnParameters SpawnInfo;

	Impulse = PartVisual->RelativeLocation;
	Impulse.Normalize();
	Impulse *= ExplosionForce;

	// Location and Rotation is not correctly set here, but getting the location and rotation from the component in world space does not work?
	ACubeDebris* debris = World->SpawnActor<ACubeDebris>(DebrisClass, Location, GetActorRotation(), SpawnInfo);
	debris->SetReplicates(false);
	debris->SetActorScale3D(PartVisual->GetComponentScale());

	debris->CubeVisual->AddImpulse(Impulse);

	FLinearColor PartColor;

	if (PartVisual->GetMaterial(0)->GetVectorParameterValue("Base Color", PartColor))
	{
		debris->SetDebrisColor(PartColor);
	}
}

void ADestroyableObstacle::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ADestroyableObstacle, Health);
}<commit_msg>Fixed the crash-to-desktop-bug<commit_after>// Fill out your copyright notice in the Description page of Project Settings.

#include "CubeWars.h"
#include "DestroyableObstacle.h"
#include "CubeDebris.h"
#include "ObstacleMovementComponent.h"
#include "Net/UnrealNetwork.h"
#include "CubeWarsGameMode.h"

namespace {
	const FVector CUBE_DIMENSIONS(20.0f, 20.0f, 20.0f);
	const int32 NUM_PARTS_PER_COLUMN = 4;
}

// Sets default values
ADestroyableObstacle::ADestroyableObstacle()
	: MinConstructionTime(0.5f)
	, MaxConstructionTime(1.0f)
	, MinConstructionDistanceMultiplier(2.0f)
	, MaxConstructionDistanceMultiplier(3.0f)
	, NumPartColumns(5)
	, Dimensions(20.0f, 300.0f, 50.0f)
	, ExplosionForce(5000.0f)
	, MaxHealth(26.0f)
	, Health(MaxHealth)
	, TotalConstructionTime(MaxConstructionTime)
	, ConstructionTimer(0.0f)
	, FinalPartMaterial(nullptr)
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Basic setup
	bReplicates = true;
	bReplicateMovement = true;

	BaseCollisionComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootComponent"));
	RootComponent = BaseCollisionComponent;
	//BaseCollisionComponent->InitBoxExtent(FVector(10.0f));
	BaseCollisionComponent->SetBoxExtent(Dimensions / 2.0f, true);
	BaseCollisionComponent->SetCollisionProfileName(TEXT("Obstacle"));
	BaseCollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	BaseCollisionComponent->SetSimulatePhysics(false);

	// Add the visual representations
	//The mesh is 20x20x20 -> match the parts to be the size of Dimensions in total
	const FVector DIMENSION_RATIO = Dimensions / CUBE_DIMENSIONS;
	const float CENTER_WIDTH = Dimensions.Y - (Dimensions.Y / NumPartColumns);
	const float X_OFFSET = Dimensions.X / 4.0f;
	const float Z_OFFSET = Dimensions.Z / 4.0f;

	FVector PartScale(DIMENSION_RATIO.X / 2.0f, (Dimensions.Y / NumPartColumns) / CUBE_DIMENSIONS.Y, DIMENSION_RATIO.Z / 2.0f);

	{
		// Create and position a mesh component so we can see where our cube is
		static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/Meshes/SimpleCube.SimpleCube"));
		if (CubeVisualAsset.Succeeded())
		{
			static ConstructorHelpers::FObjectFinder<UMaterialInstance> Material(TEXT("/Game/Materials/QuadraticTransparent.QuadraticTransparent"));

			if (Material.Succeeded())
			{
				for (int32 i = 0; i < NumPartColumns; ++i)
				{
					const float CENTER_Y = CENTER_WIDTH * (i / static_cast<float>(NumPartColumns - 1)) - CENTER_WIDTH / 2.0f;

					TArray<UStaticMeshComponent*> NewVisuals;

					NewVisuals.Add(CreateObstaclePart(FVector(X_OFFSET, CENTER_Y, Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN));
					NewVisuals.Add(CreateObstaclePart(FVector(X_OFFSET, CENTER_Y, -Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 1));
					NewVisuals.Add(CreateObstaclePart(FVector(-X_OFFSET, CENTER_Y, -Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 2));
					NewVisuals.Add(CreateObstaclePart(FVector(-X_OFFSET, CENTER_Y, Z_OFFSET), PartScale, CubeVisualAsset.Object, Material.Object, i * NUM_PARTS_PER_COLUMN + 3));

					FRandomStream RandStream;
					RandStream.GenerateNewSeed();

					// First determine the parts that can fall off
					int32 Index = RandStream.RandRange(0, 3);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					Index = RandStream.RandRange(0, 2);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					Index = RandStream.RandRange(0, 1);
					FallOffParts.Add(NewVisuals[Index]);
					NewVisuals.RemoveAt(Index);

					// Now the fixed parts
					FixedParts.Append(NewVisuals);
				}
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Materials/QuadraticTransparent.QuadraticTransparent'!"));
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Meshes/SimpleCube.SimpleCube'!"));
		}
	}

	// Load the material FinalPartMaterial
	static ConstructorHelpers::FObjectFinder<UMaterialInstance> Material(TEXT("/Game/Materials/Quadratic.Quadratic"));
	if (Material.Succeeded())
	{
		FinalPartMaterial = Material.Object;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Cannot find '/Game/Materials/Quadratic.Quadratic'!"));
	}

	// Movement component
	MovementComponent = CreateDefaultSubobject<UObstacleMovementComponent>(TEXT("ObstacleMovement"));
	MovementComponent->UpdatedComponent = RootComponent;
}

// Called when the game starts or when spawned
void ADestroyableObstacle::BeginPlay()
{
	Super::BeginPlay();

	TotalConstructionTime = MaxConstructionTime;

	// Initialize the construction structs for the parts of the obstacle
	FRandomStream Rand;
	for (int32 i = 0; i < ConstructionProgresses.Num(); ++i)
	{
		PartConstructionProgress& PartProgress = ConstructionProgresses[i];
		PartProgress.StartPosition = PartProgress.FinalPosition * Rand.FRandRange(MinConstructionDistanceMultiplier, MaxConstructionDistanceMultiplier);
		PartProgress.TotalTime = Rand.FRandRange(MinConstructionTime, MaxConstructionTime);

		UMaterialInstanceDynamic* MaterialInstance = PartProgress.CubeVisual->CreateDynamicMaterialInstance(0);
		PartProgress.CubeVisual->SetMaterial(0, MaterialInstance);
		PartProgress.MaterialInstance = MaterialInstance;
		MaterialInstance->SetScalarParameterValue("Opacity", 0.0f);
	}
}

// Called every frame
void ADestroyableObstacle::Tick( float DeltaTime )
{
	if(!this->IsValidLowLevel() || IsActorBeingDestroyed())
		return;

	Super::Tick( DeltaTime );

	for (int32 i = 0; i < ConstructionProgresses.Num();)
	{
		PartConstructionProgress& PartProgress = ConstructionProgresses[i];
		PartProgress.CurrentTime += DeltaTime;

		UStaticMeshComponent* CubeVisual = PartProgress.CubeVisual;
		UMaterialInstanceDynamic* MaterialInstance = PartProgress.MaterialInstance;
		FVector NewPosition;
		float NewOpacity;

		if (PartProgress.CurrentTime >= PartProgress.TotalTime)
		{
			NewPosition = PartProgress.FinalPosition;
			NewOpacity = 1.0f;
			ConstructionProgresses.RemoveAt(i);

			if (FinalPartMaterial)
			{
				CubeVisual->SetMaterial(0, FinalPartMaterial);
			}
		}
		else
		{
			float Delta = PartProgress.CurrentTime / PartProgress.TotalTime;
			NewPosition = FMath::Lerp(PartProgress.StartPosition, PartProgress.FinalPosition, Delta);
			NewOpacity = Delta;
			++i;
		}

		CubeVisual->SetRelativeLocation(NewPosition);

		if (MaterialInstance)
		{
			MaterialInstance->SetScalarParameterValue("Opacity", NewOpacity);
		}

	}
}

void ADestroyableObstacle::Destroyed()
{
	ACubeWarsGameMode* cwgm = GetWorld()->GetAuthGameMode<ACubeWarsGameMode>();
	if (cwgm)
	{
		cwgm->ObstacleDied(ObstacleIndex);
	}
}

float ADestroyableObstacle::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
	Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	if (IsConstructionInProgress())
	{
		if (Health <= 0)
		{
			return 0;
		}

		Health -= DamageAmount;

		if (Health <= 0)
		{
			// Let all the parts fly around and despawn the actor
			ClientFallApart();

			Destroy();
		}
		else
		{
			// Let some parts fly around
			ClientDamageCallback(Health);
		}

		return DamageAmount;
	}

	return 0.0f;
}

UStaticMeshComponent* ADestroyableObstacle::CreateObstaclePart(const FVector& RelativePosition, const FVector& Scale, UStaticMesh* StaticMesh, UMaterialInstance* MatInstance, int32 Number)
{
	FString VisualName("VisualRepresentation");
	VisualName.AppendInt(Number);

	UStaticMeshComponent* CubeVisual = CreateDefaultSubobject<UStaticMeshComponent>(FName(*VisualName));
	CubeVisual->AttachTo(RootComponent);
	CubeVisual->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	CubeVisual->SetCollisionProfileName(TEXT("Obstacle"));
	CubeVisual->SetSimulatePhysics(false);
	CubeVisual->SetRelativeLocation(RelativePosition);
	CubeVisual->SetStaticMesh(StaticMesh);
	CubeVisual->SetWorldScale3D(Scale);
	CubeVisual->SetMaterial(0, MatInstance);

	// Initialize the struct for the construction of th obstacle (some values (FinalPosition and TotalTime) will be recalculated, in BeginPlay, when the values from the blueprint are known)
	PartConstructionProgress PartConstruction(RelativePosition, RelativePosition, CubeVisual, nullptr, 1.0f);
	ConstructionProgresses.Add(PartConstruction);

	return CubeVisual;
}

void ADestroyableObstacle::ClientDamageCallback_Implementation(float NewHealth)
{
	if (!IsRunningDedicatedServer())
	{
		const int32 NUM_TOTAL_FALLOFF_PARTS = ((NumPartColumns * NUM_PARTS_PER_COLUMN) * 3) / 4;
		int32 RemainingParts = static_cast<int32>(NUM_TOTAL_FALLOFF_PARTS * (NewHealth / MaxHealth));

		int32 NumPartsToFallOff = FMath::Max(0, FallOffParts.Num() - RemainingParts);

		FRandomStream RandStream;
		RandStream.GenerateNewSeed();

		for (int32 i = 0; i < NumPartsToFallOff; ++i)
		{
			int32 Index = RandStream.RandRange(0, FallOffParts.Num() - 1);

			UStaticMeshComponent* PartVisual = FallOffParts[Index];
			FallOffParts.RemoveAt(Index);

			CreateDebris(PartVisual);
		}
	}
}

void ADestroyableObstacle::ClientFallApart_Implementation()
{
	for (int32 i = 0; i < FallOffParts.Num(); ++i)
	{
		UStaticMeshComponent* PartVisual = FallOffParts[i];
		CreateDebris(PartVisual);
	}
	FallOffParts.Empty();

	for (int32 i = 0; i < FixedParts.Num(); ++i)
	{
		UStaticMeshComponent* PartVisual = FixedParts[i];
		CreateDebris(PartVisual);
	}
	FixedParts.Empty();
}

void ADestroyableObstacle::CreateDebris(UStaticMeshComponent* PartVisual)
{
	// Destroy the component...
	UWorld* const World = GetWorld();
	PartVisual->DestroyComponent();

	// ...and spawn the actors instead
	FVector Impulse;
	const FVector& ActorLocation = GetActorLocation();
	FVector Location(ActorLocation.X + PartVisual->RelativeLocation.X, ActorLocation.Y + PartVisual->RelativeLocation.Y, ActorLocation.Z + PartVisual->RelativeLocation.Z);
	FActorSpawnParameters SpawnInfo;

	Impulse = PartVisual->RelativeLocation;
	Impulse.Normalize();
	Impulse *= ExplosionForce;

	// Location and Rotation is not correctly set here, but getting the location and rotation from the component in world space does not work?
	ACubeDebris* debris = World->SpawnActor<ACubeDebris>(DebrisClass, Location, GetActorRotation(), SpawnInfo);
	debris->SetReplicates(false);
	debris->SetActorScale3D(PartVisual->GetComponentScale());

	debris->CubeVisual->AddImpulse(Impulse);

	FLinearColor PartColor;

	if (PartVisual->GetMaterial(0)->GetVectorParameterValue("Base Color", PartColor))
	{
		debris->SetDebrisColor(PartColor);
	}
}

void ADestroyableObstacle::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ADestroyableObstacle, Health);
}<|endoftext|>