diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index baa7cc1b8c8a04e3c85ff804ede8fc2d21746eab..b0309d274a91d3dff1acb5a34ace1a8e61a62cf0 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -112,6 +112,7 @@ include_directories(SYSTEM
 set(viewer_SOURCE_FILES
     alavataractions.cpp
     alchatcommand.cpp
+    alfloaterparticleeditor.cpp
     alunzip.cpp
     alviewermenu.cpp
     groupchatlistener.cpp
@@ -750,6 +751,7 @@ set(viewer_HEADER_FILES
     ViewerInstall.cmake
     alavataractions.h
     alchatcommand.h
+    alfloaterparticleeditor.h
     alunzip.h
     alviewermenu.h
     groupchatlistener.h
diff --git a/indra/newview/alfloaterparticleeditor.cpp b/indra/newview/alfloaterparticleeditor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba1a018adeb7fc179feaa707088d9502b26a70c7
--- /dev/null
+++ b/indra/newview/alfloaterparticleeditor.cpp
@@ -0,0 +1,645 @@
+/**
+ * @file alfloaterparticleeditor.cpp
+ * @brief Particle Editor
+ *
+ * Copyright (C) 2011, Zi Ree @ Second Life
+ * Copyright (C) 2015, Cinder Roxley <cinder@sdf.org>
+ * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "alfloaterparticleeditor.h"
+
+#include "llagent.h"
+#include "llappviewer.h"
+#include "llcheckboxctrl.h"
+#include "llclipboard.h"
+#include "llcolorswatch.h"
+#include "llcombobox.h"
+#include "llfoldertype.h"
+#include "llinventorymodel.h"
+#include "llinventorytype.h"
+#include "llnotificationsutil.h"
+#include "llpermissions.h"
+#include "llpreviewscript.h"
+#include "llsd.h"
+#include "lltexturectrl.h"
+#include "lltoolmgr.h"
+#include "lltoolobjpicker.h"
+#include "llviewerassetupload.h"
+#include "llviewerobject.h"
+#include "llviewerobjectlist.h"
+#include "llviewerpartsim.h"
+#include "llviewerpartsource.h"
+#include "llviewerregion.h"
+#include "llviewertexture.h"
+#include "llwindow.h"
+
+struct lsl_part_st {
+	U8 flag;
+	const char* script_const;
+	lsl_part_st(const U8 f, const char* sc)
+	{
+		flag = f, script_const = sc;
+	}
+};
+
+static const std::map<std::string, lsl_part_st> sParticlePatterns{
+	{ "drop", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_DROP, "PSYS_SRC_PATTERN_DROP") },
+	{ "explode", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE, "PSYS_SRC_PATTERN_EXPLODE") },
+	{ "angle", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE, "PSYS_SRC_PATTERN_ANGLE") },
+	{ "angle_cone", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE, "PSYS_SRC_PATTERN_ANGLE_CONE") },
+	{ "angle_cone_empty", lsl_part_st(LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE_EMPTY, "PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY") }
+};
+
+static const std::map<std::string, lsl_part_st> sParticleBlends{
+	{ "blend_one", lsl_part_st(LLPartData::LL_PART_BF_ONE, "PSYS_PART_BF_ONE") },
+	{ "blend_zero", lsl_part_st(LLPartData::LL_PART_BF_ZERO, "PSYS_PART_BF_ZERO") },
+	{ "blend_dest_color", lsl_part_st(LLPartData::LL_PART_BF_DEST_COLOR, "PSYS_PART_BF_DEST_COLOR") },
+	{ "blend_src_color", lsl_part_st(LLPartData::LL_PART_BF_SOURCE_COLOR, "PSYS_PART_BF_SOURCE_COLOR") },
+	{ "blend_one_minus_dest_color", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_DEST_COLOR, "PSYS_PART_BF_ONE_MINUS_DEST_COLOR") },
+	{ "blend_one_minus_src_color", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_COLOR, "PSYS_PART_BF_SOURCE_ALPHA") },
+	{ "blend_src_alpha", lsl_part_st(LLPartData::LL_PART_BF_SOURCE_ALPHA, "PSYS_PART_BF_SOURCE_ALPHA") },
+	{ "blend_one_minus_src_alpha", lsl_part_st(LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA, "PSYS_PART_BF_ONE_MINUS_SOURCE_ALPHA") }
+};
+
+static const std::string PARTICLE_SCRIPT_NAME = "New Particle Script";
+
+ALFloaterParticleEditor::ALFloaterParticleEditor(const LLSD& key)
+	: LLFloater(key), mChanged(false), mCloseAfterSave(false), mObject(nullptr), mTexture(nullptr)
+	, mParticleScriptInventoryItem(nullptr), mPatternTypeCombo(nullptr), mTexturePicker(nullptr)
+	, mBurstRateCtrl(nullptr), mBurstCountCtrl(nullptr), mBurstRadiusCtrl(nullptr)
+	, mAngleBeginCtrl(nullptr), mAngleEndCtrl(nullptr), mBurstSpeedMinCtrl(nullptr)
+	, mBurstSpeedMaxCtrl(nullptr), mStartAlphaCtrl(nullptr), mEndAlphaCtrl(nullptr)
+	, mScaleStartXCtrl(nullptr), mScaleStartYCtrl(nullptr), mScaleEndXCtrl(nullptr)
+	, mScaleEndYCtrl(nullptr), mSourceMaxAgeCtrl(nullptr), mParticlesMaxAgeCtrl(nullptr)
+	, mStartGlowCtrl(nullptr), mEndGlowCtrl(nullptr), mAcellerationXCtrl(nullptr)
+	, mAcellerationYCtrl(nullptr), mAcellerationZCtrl(nullptr), mOmegaXCtrl(nullptr)
+	, mOmegaYCtrl(nullptr), mOmegaZCtrl(nullptr), mBlendFuncSrcCombo(nullptr)
+	, mBlendFuncDestCombo(nullptr), mBounceCheckBox(nullptr), mEmissiveCheckBox(nullptr)
+	, mFollowSourceCheckBox(nullptr), mFollowVelocityCheckBox(nullptr), mInterpolateColorCheckBox(nullptr)
+	, mInterpolateScaleCheckBox(nullptr), mTargetPositionCheckBox(nullptr), mTargetLinearCheckBox(nullptr)
+	, mWindCheckBox(nullptr), mRibbonCheckBox(nullptr), mTargetKeyInput(nullptr)
+	, mClearTargetButton(nullptr), mPickTargetButton(nullptr), mCopyToLSLButton(nullptr)
+	, mInjectScriptButton(nullptr), mStartColorSelector(nullptr), mEndColorSelector(nullptr)
+{
+	mCommitCallbackRegistrar.add("Particle.Edit", [this](LLUICtrl* ctrl, const LLSD& param) { onParameterChange(); });
+	mDefaultParticleTexture = LLViewerFetchedTexture::sPixieSmallImagep;
+}
+
+ALFloaterParticleEditor::~ALFloaterParticleEditor()
+{
+	clearParticles();
+}
+
+BOOL ALFloaterParticleEditor::postBuild()
+{
+	LLPanel* panel = getChild<LLPanel>("burst_panel");
+	mBurstRateCtrl = panel->getChild<LLUICtrl>("burst_rate");
+	mBurstCountCtrl = panel->getChild<LLUICtrl>("burst_count");
+	mBurstRadiusCtrl = panel->getChild<LLUICtrl>("burst_radius");
+	mBurstSpeedMinCtrl = panel->getChild<LLUICtrl>("burst_speed_min");
+	mBurstSpeedMaxCtrl = panel->getChild<LLUICtrl>("burst_speed_max");
+	mSourceMaxAgeCtrl = panel->getChild<LLUICtrl>("source_max_age");
+	mParticlesMaxAgeCtrl = panel->getChild<LLUICtrl>("particle_max_age");
+
+	panel = getChild<LLPanel>("angle_panel");
+	mAngleBeginCtrl = panel->getChild<LLUICtrl>("angle_begin");
+	mAngleEndCtrl = panel->getChild<LLUICtrl>("angle_end");
+	mScaleStartXCtrl = panel->getChild<LLUICtrl>("scale_start_x");
+	mScaleStartYCtrl = panel->getChild<LLUICtrl>("scale_start_y");
+	mScaleEndXCtrl = panel->getChild<LLUICtrl>("scale_end_x");
+	mScaleEndYCtrl = panel->getChild<LLUICtrl>("scale_end_y");
+
+	panel = getChild<LLPanel>("alpha_panel");
+	mStartAlphaCtrl = panel->getChild<LLUICtrl>("start_alpha");
+	mEndAlphaCtrl = panel->getChild<LLUICtrl>("end_alpha");
+	mStartGlowCtrl = panel->getChild<LLUICtrl>("start_glow");
+	mEndGlowCtrl = panel->getChild<LLUICtrl>("end_glow");
+
+	panel = getChild<LLPanel>("omega_panel");
+	mAcellerationXCtrl = panel->getChild<LLUICtrl>("acceleration_x");
+	mAcellerationYCtrl = panel->getChild<LLUICtrl>("acceleration_y");
+	mAcellerationZCtrl = panel->getChild<LLUICtrl>("acceleration_z");
+	mOmegaXCtrl = panel->getChild<LLUICtrl>("omega_x");
+	mOmegaYCtrl = panel->getChild<LLUICtrl>("omega_y");
+	mOmegaZCtrl = panel->getChild<LLUICtrl>("omega_z");
+
+	panel = getChild<LLPanel>("color_panel");
+	mStartColorSelector = panel->getChild<LLColorSwatchCtrl>("start_color_selector");
+	mEndColorSelector = panel->getChild<LLColorSwatchCtrl>("end_color_selector");
+	mTexturePicker = panel->getChild<LLTextureCtrl>("texture_picker");
+	mBlendFuncSrcCombo = panel->getChild<LLComboBox>("blend_func_src_combo");
+	mBlendFuncDestCombo = panel->getChild<LLComboBox>("blend_func_dest_combo");
+
+	panel = getChild<LLPanel>("checkbox_panel");
+	mPatternTypeCombo = panel->getChild<LLComboBox>("pattern_type_combo");
+	mBounceCheckBox = panel->getChild<LLCheckBoxCtrl>("bounce_checkbox");
+	mEmissiveCheckBox = panel->getChild<LLCheckBoxCtrl>("emissive_checkbox");
+	mFollowSourceCheckBox = panel->getChild<LLCheckBoxCtrl>("follow_source_checkbox");
+	mFollowVelocityCheckBox = panel->getChild<LLCheckBoxCtrl>("follow_velocity_checkbox");
+	mInterpolateColorCheckBox = panel->getChild<LLCheckBoxCtrl>("interpolate_color_checkbox");
+	mInterpolateScaleCheckBox = panel->getChild<LLCheckBoxCtrl>("interpolate_scale_checkbox");
+	mTargetPositionCheckBox = panel->getChild<LLCheckBoxCtrl>("target_position_checkbox");
+	mTargetLinearCheckBox = panel->getChild<LLCheckBoxCtrl>("target_linear_checkbox");
+	mWindCheckBox = panel->getChild<LLCheckBoxCtrl>("wind_checkbox");
+	mRibbonCheckBox = panel->getChild<LLCheckBoxCtrl>("ribbon_checkbox");
+
+	mTargetKeyInput = panel->getChild<LLUICtrl>("target_key_input");
+	mClearTargetButton = panel->getChild<LLButton>("clear_target_button");
+	mPickTargetButton = panel->getChild<LLButton>("pick_target_button");
+	mInjectScriptButton = panel->getChild<LLButton>("inject_button");
+	mCopyToLSLButton = panel->getChild<LLButton>("copy_button");
+
+	mClearTargetButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickClearTarget(); });
+	mPickTargetButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickTargetPicker(); });
+	mInjectScriptButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { injectScript(); });
+	mCopyToLSLButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onClickCopy(); });
+
+	mBlendFuncSrcCombo->setValue("blend_src_alpha");
+	mBlendFuncDestCombo->setValue("blend_one_minus_src_alpha");
+
+	onParameterChange();
+
+	return TRUE;
+}
+
+BOOL ALFloaterParticleEditor::canClose()
+{
+    if (!hasChanged())
+    {
+        return TRUE;
+    }
+    else
+    {
+        // Bring up view-modal dialog: Save changes? Yes, No, Cancel
+        LLNotificationsUtil::add("ParticleSaveChanges", LLSD(), LLSD(), boost::bind(&ALFloaterParticleEditor::handleSaveDialog, this, _1, _2));
+        return FALSE;
+    }
+}
+
+bool ALFloaterParticleEditor::handleSaveDialog(const LLSD& notification, const LLSD& response)
+{
+    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+    switch (option)
+    {
+    case 0:  // "Yes"
+             // close after saving
+        mCloseAfterSave = true;
+        injectScript();
+        break;
+
+    case 1:  // "No"
+		mChanged = false;
+        closeFloater();
+        break;
+
+    case 2: // "Cancel"
+    default:
+        break;
+    }
+    return false;
+}
+
+void ALFloaterParticleEditor::clearParticles()
+{
+	if (!mObject)
+		return;
+
+	LL_DEBUGS("ParticleEditor") << "clearing particles from " << mObject->getID() << LL_ENDL;
+
+	LLViewerPartSim::getInstance()->clearParticlesByOwnerID(mObject->getID());
+}
+
+void ALFloaterParticleEditor::updateParticles()
+{
+	if (!mObject)
+		return;
+
+	clearParticles();
+	LLPointer<LLViewerPartSourceScript> pss = LLViewerPartSourceScript::createPSS(mObject, mParticles);
+
+	pss->setOwnerUUID(mObject->getID());
+	pss->setImage(mTexture);
+
+	LLViewerPartSim::getInstance()->addPartSource(pss);
+}
+
+void ALFloaterParticleEditor::setObject(LLViewerObject* objectp)
+{
+	if (objectp)
+	{
+		mObject = objectp;
+
+		LL_DEBUGS("ParticleEditor") << "adding particles to " << mObject->getID() << LL_ENDL;
+
+		updateParticles();
+	}
+}
+
+void ALFloaterParticleEditor::onParameterChange()
+{
+    mChanged = true;
+	mParticles.mPattern = sParticlePatterns.at(mPatternTypeCombo->getSelectedValue()).flag;
+	mParticles.mPartImageID = mTexturePicker->getImageAssetID();
+
+	// remember the selected texture here to give updateParticles() a UUID to work with
+	mTexture = LLViewerTextureManager::getFetchedTexture(mTexturePicker->getImageAssetID());
+
+	if (mTexture->getID() == IMG_DEFAULT || mTexture->getID().isNull())
+	{
+		mTexture = mDefaultParticleTexture;
+	}
+
+	// limit burst rate to 0.01 to avoid internal freeze, script still gets the real value
+	mParticles.mBurstRate = llmax<float>(0.01f, mBurstRateCtrl->getValue().asReal());
+	mParticles.mBurstPartCount = mBurstCountCtrl->getValue().asInteger();
+	mParticles.mBurstRadius = mBurstRadiusCtrl->getValue().asReal();
+	mParticles.mInnerAngle = mAngleBeginCtrl->getValue().asReal();
+	mParticles.mOuterAngle = mAngleEndCtrl->getValue().asReal();
+	mParticles.mBurstSpeedMin = mBurstSpeedMinCtrl->getValue().asReal();
+	mParticles.mBurstSpeedMax = mBurstSpeedMaxCtrl->getValue().asReal();
+	mParticles.mPartData.setStartAlpha(mStartAlphaCtrl->getValue().asReal());
+	mParticles.mPartData.setEndAlpha(mEndAlphaCtrl->getValue().asReal());
+	mParticles.mPartData.setStartScale(mScaleStartXCtrl->getValue().asReal(),
+					   mScaleStartYCtrl->getValue().asReal());
+	mParticles.mPartData.setEndScale(mScaleEndXCtrl->getValue().asReal(),
+					 mScaleEndYCtrl->getValue().asReal());
+	mParticles.mMaxAge = mSourceMaxAgeCtrl->getValue().asReal();
+	mParticles.mPartData.setMaxAge(mParticlesMaxAgeCtrl->getValue().asReal());
+
+	mParticles.mPartData.mStartGlow = mStartGlowCtrl->getValue().asReal();
+	mParticles.mPartData.mEndGlow = mEndGlowCtrl->getValue().asReal();
+
+	mParticles.mPartData.mBlendFuncSource = sParticleBlends.at(mBlendFuncSrcCombo->getSelectedValue()).flag;
+	mParticles.mPartData.mBlendFuncDest = sParticleBlends.at(mBlendFuncDestCombo->getSelectedValue()).flag;
+
+	U32 flags = 0;
+	if (mBounceCheckBox->getValue().asBoolean())			flags |= LLPartData::LL_PART_BOUNCE_MASK;
+	if (mEmissiveCheckBox->getValue().asBoolean())			flags |= LLPartData::LL_PART_EMISSIVE_MASK;
+	if (mFollowSourceCheckBox->getValue().asBoolean())		flags |= LLPartData::LL_PART_FOLLOW_SRC_MASK;
+	if (mFollowVelocityCheckBox->getValue().asBoolean())	flags |= LLPartData::LL_PART_FOLLOW_VELOCITY_MASK;
+	if (mInterpolateColorCheckBox->getValue().asBoolean())	flags |= LLPartData::LL_PART_INTERP_COLOR_MASK;
+	if (mInterpolateScaleCheckBox->getValue().asBoolean())	flags |= LLPartData::LL_PART_INTERP_SCALE_MASK;
+	if (mTargetPositionCheckBox->getValue().asBoolean())	flags |= LLPartData::LL_PART_TARGET_POS_MASK;
+	if (mTargetLinearCheckBox->getValue().asBoolean())		flags |= LLPartData::LL_PART_TARGET_LINEAR_MASK;
+	if (mWindCheckBox->getValue().asBoolean())				flags |= LLPartData::LL_PART_WIND_MASK;
+	if (mRibbonCheckBox->getValue().asBoolean())			flags |= LLPartData::LL_PART_RIBBON_MASK;
+	mParticles.mPartData.setFlags(flags);
+	mParticles.setUseNewAngle();
+
+	mParticles.mTargetUUID = mTargetKeyInput->getValue().asUUID();
+
+	mParticles.mPartAccel = LLVector3(mAcellerationXCtrl->getValue().asReal(),
+					  mAcellerationYCtrl->getValue().asReal(),
+					  mAcellerationZCtrl->getValue().asReal());
+	mParticles.mAngularVelocity = LLVector3(mOmegaXCtrl->getValue().asReal(),
+						mOmegaYCtrl->getValue().asReal(),
+						mOmegaZCtrl->getValue().asReal());
+
+	LLColor4 color = mStartColorSelector->get();
+	mParticles.mPartData.setStartColor(LLVector3(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]));
+	color = mEndColorSelector->get();
+	mParticles.mPartData.setEndColor(LLVector3(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]));
+
+	updateUI();
+	updateParticles();
+}
+
+void ALFloaterParticleEditor::updateUI()
+{
+	U8 pattern = sParticlePatterns.at(mPatternTypeCombo->getValue()).flag;
+	BOOL drop_pattern = (pattern == LLPartSysData::LL_PART_SRC_PATTERN_DROP);
+	BOOL explode_pattern = (pattern == LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE);
+	BOOL target_linear = mTargetLinearCheckBox->getValue();
+	BOOL interpolate_color = mInterpolateColorCheckBox->getValue();
+	BOOL interpolate_scale = mInterpolateScaleCheckBox->getValue();
+	BOOL target_enabled = target_linear | (mTargetPositionCheckBox->getValue().asBoolean() ? TRUE : FALSE);
+
+	mBurstRadiusCtrl->setEnabled(!(target_linear | (mFollowSourceCheckBox->getValue().asBoolean() ? TRUE : FALSE) | drop_pattern));
+	mBurstSpeedMinCtrl->setEnabled(!(target_linear | drop_pattern));
+	mBurstSpeedMaxCtrl->setEnabled(!(target_linear | drop_pattern));
+
+	// disabling a color swatch does nothing visually, so we also set alpha
+	LLColor4 end_color = mEndColorSelector->get();
+	end_color.setAlpha(interpolate_color ? 1.0f : 0.0f);
+
+	mEndAlphaCtrl->setEnabled(interpolate_color);
+	mEndColorSelector->set(end_color);
+	mEndColorSelector->setEnabled(interpolate_color);
+
+	mScaleEndXCtrl->setEnabled(interpolate_scale);
+	mScaleEndYCtrl->setEnabled(interpolate_scale);
+
+	mTargetPositionCheckBox->setEnabled(!target_linear);
+	mTargetKeyInput->setEnabled(target_enabled);
+	mPickTargetButton->setEnabled(target_enabled);
+	mClearTargetButton->setEnabled(target_enabled);
+
+	mAcellerationXCtrl->setEnabled(!target_linear);
+	mAcellerationYCtrl->setEnabled(!target_linear);
+	mAcellerationZCtrl->setEnabled(!target_linear);
+
+	mOmegaXCtrl->setEnabled(!target_linear);
+	mOmegaYCtrl->setEnabled(!target_linear);
+	mOmegaZCtrl->setEnabled(!target_linear);
+
+	mAngleBeginCtrl->setEnabled(!(explode_pattern | drop_pattern));
+	mAngleEndCtrl->setEnabled(!(explode_pattern | drop_pattern));
+}
+
+void ALFloaterParticleEditor::onClickClearTarget()
+{
+	mTargetKeyInput->clear();
+	onParameterChange();
+}
+
+void ALFloaterParticleEditor::onClickTargetPicker()
+{
+	mPickTargetButton->setToggleState(TRUE);
+	mPickTargetButton->setEnabled(FALSE);
+	LLToolObjPicker::getInstance()->setExitCallback(onTargetPicked, this);
+	LLToolMgr::getInstance()->setTransientTool(LLToolObjPicker::getInstance());
+}
+
+// static
+void ALFloaterParticleEditor::onTargetPicked(void* userdata)
+{
+	ALFloaterParticleEditor* self = static_cast<ALFloaterParticleEditor*>(userdata);
+
+	const LLUUID picked = LLToolObjPicker::getInstance()->getObjectID();
+
+	LLToolMgr::getInstance()->clearTransientTool();
+
+	self->mPickTargetButton->setEnabled(TRUE);
+	self->mPickTargetButton->setToggleState(FALSE);
+
+	if (picked.notNull())
+	{
+		self->mTargetKeyInput->setValue(picked.asString());
+		self->onParameterChange();
+	}
+}
+
+/* static */
+std::string ALFloaterParticleEditor::lslVector(const F32 x, const F32 y, const F32 z)
+{
+	return llformat("<%f,%f,%f>", x, y, z);
+}
+
+/* static */
+std::string ALFloaterParticleEditor::lslColor(const LLColor4& color)
+{
+	return lslVector(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]);
+}
+
+std::string ALFloaterParticleEditor::createScript()
+{
+	std::string script(
+"\
+default\n\
+{\n\
+    state_entry()\n\
+    {\n\
+        llParticleSystem(\n\
+        [\n\
+            PSYS_SRC_PATTERN,[PATTERN],\n\
+            PSYS_SRC_BURST_RADIUS,[BURST_RADIUS],\n\
+            PSYS_SRC_ANGLE_BEGIN,[ANGLE_BEGIN],\n\
+            PSYS_SRC_ANGLE_END,[ANGLE_END],\n\
+            PSYS_SRC_TARGET_KEY,[TARGET_KEY],\n\
+            PSYS_PART_START_COLOR,[START_COLOR],\n\
+            PSYS_PART_END_COLOR,[END_COLOR],\n\
+            PSYS_PART_START_ALPHA,[START_ALPHA],\n\
+            PSYS_PART_END_ALPHA,[END_ALPHA],\n\
+            PSYS_PART_START_GLOW,[START_GLOW],\n\
+            PSYS_PART_END_GLOW,[END_GLOW],\n\
+            PSYS_PART_BLEND_FUNC_SOURCE,[BLEND_FUNC_SOURCE],\n\
+            PSYS_PART_BLEND_FUNC_DEST,[BLEND_FUNC_DEST],\n\
+            PSYS_PART_START_SCALE,[START_SCALE],\n\
+            PSYS_PART_END_SCALE,[END_SCALE],\n\
+            PSYS_SRC_TEXTURE,\"[TEXTURE]\",\n\
+            PSYS_SRC_MAX_AGE,[SOURCE_MAX_AGE],\n\
+            PSYS_PART_MAX_AGE,[PART_MAX_AGE],\n\
+            PSYS_SRC_BURST_RATE,[BURST_RATE],\n\
+            PSYS_SRC_BURST_PART_COUNT,[BURST_COUNT],\n\
+            PSYS_SRC_ACCEL,[ACCELERATION],\n\
+            PSYS_SRC_OMEGA,[OMEGA],\n\
+            PSYS_SRC_BURST_SPEED_MIN,[BURST_SPEED_MIN],\n\
+            PSYS_SRC_BURST_SPEED_MAX,[BURST_SPEED_MAX],\n\
+            PSYS_PART_FLAGS,\n\
+                0[FLAGS]\n\
+        ]);\n\
+    }\n\
+}\n");
+
+	const LLUUID target_key = mTargetKeyInput->getValue().asUUID();
+	std::string key_string = "llGetKey()";
+
+	if (target_key.notNull() && target_key != mObject->getID())
+	{
+		key_string = "(key) \"" + target_key.asString() + "\"";
+	}
+
+	LLUUID texture_key = mTexture->getID();
+	std::string texture_string;
+	if (texture_key.notNull() && texture_key != IMG_DEFAULT && texture_key != mDefaultParticleTexture->getID())
+	{
+		texture_string = texture_key.asString();
+	}
+
+	LLStringUtil::replaceString(script, "[PATTERN]", sParticlePatterns.at(mPatternTypeCombo->getValue()).script_const);
+	LLStringUtil::replaceString(script, "[BURST_RADIUS]", mBurstRadiusCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[ANGLE_BEGIN]", mAngleBeginCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[ANGLE_END]", mAngleEndCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[TARGET_KEY]", key_string);
+	LLStringUtil::replaceString(script, "[START_COLOR]", lslColor(mStartColorSelector->get()));
+	LLStringUtil::replaceString(script, "[END_COLOR]", lslColor(mEndColorSelector->get()));
+	LLStringUtil::replaceString(script, "[START_ALPHA]", mStartAlphaCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[END_ALPHA]", mEndAlphaCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[START_GLOW]", mStartGlowCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[END_GLOW]", mEndGlowCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[START_SCALE]", lslVector(mScaleStartXCtrl->getValue().asReal(),
+								       mScaleStartYCtrl->getValue().asReal(),
+								       0.0f));
+	LLStringUtil::replaceString(script, "[END_SCALE]", lslVector(mScaleEndXCtrl->getValue().asReal(),
+								     mScaleEndYCtrl->getValue().asReal(),
+								     0.0f));
+	LLStringUtil::replaceString(script, "[TEXTURE]", texture_string);
+	LLStringUtil::replaceString(script, "[SOURCE_MAX_AGE]", mSourceMaxAgeCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[PART_MAX_AGE]", mParticlesMaxAgeCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[BURST_RATE]", mBurstRateCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[BURST_COUNT]", mBurstCountCtrl->getValue());
+	LLStringUtil::replaceString(script, "[ACCELERATION]", lslVector(mAcellerationXCtrl->getValue().asReal(),
+									mAcellerationYCtrl->getValue().asReal(),
+									mAcellerationZCtrl->getValue().asReal()));
+	LLStringUtil::replaceString(script, "[OMEGA]", lslVector(mOmegaXCtrl->getValue().asReal(),
+								 mOmegaYCtrl->getValue().asReal(),
+								 mOmegaZCtrl->getValue().asReal()));
+	LLStringUtil::replaceString(script, "[BURST_SPEED_MIN]", mBurstSpeedMinCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[BURST_SPEED_MAX]", mBurstSpeedMaxCtrl->getValue().asString());
+	LLStringUtil::replaceString(script, "[BLEND_FUNC_SOURCE]", sParticleBlends.at(mBlendFuncSrcCombo->getValue().asString()).script_const);
+	LLStringUtil::replaceString(script, "[BLEND_FUNC_DEST]", sParticleBlends.at(mBlendFuncDestCombo->getValue().asString()).script_const);
+
+    const std::string delimiter = " |\n                ";
+	std::string flags_string = "";
+
+	if (mBounceCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_BOUNCE_MASK";
+	if (mEmissiveCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_EMISSIVE_MASK";
+	if (mFollowSourceCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_FOLLOW_SRC_MASK";
+	if (mFollowVelocityCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_FOLLOW_VELOCITY_MASK";
+	if (mInterpolateColorCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_INTERP_COLOR_MASK";
+	if (mInterpolateScaleCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_INTERP_SCALE_MASK";
+	if (mTargetLinearCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_TARGET_LINEAR_MASK";
+	if (mTargetPositionCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_TARGET_POS_MASK";
+	if (mWindCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_WIND_MASK";
+	if (mRibbonCheckBox->getValue())
+		flags_string += delimiter + "PSYS_PART_RIBBON_MASK";
+
+	LLStringUtil::replaceString(script, "[FLAGS]", flags_string);
+	LL_DEBUGS("ParticleEditor") << "\n" << script << LL_ENDL;
+
+	return script;
+}
+
+void ALFloaterParticleEditor::onClickCopy()
+{
+    mChanged = false;
+	const std::string script = createScript();
+	if (!script.empty())
+	{
+		getWindow()->copyTextToClipboard(utf8str_to_wstring(script));
+		LLNotificationsUtil::add("ParticleScriptCopiedToClipboard");
+	}
+}
+
+void ALFloaterParticleEditor::injectScript()
+{
+    mChanged = false;
+	const LLUUID categoryID = gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT);
+
+	// if no valid folder found bail out and complain
+	if (categoryID.isNull())
+	{
+		LLNotificationsUtil::add("ParticleScriptFindFolderFailed");
+		return;
+	}
+
+	// setup permissions
+	LLPermissions perm;
+	perm.init(gAgentID, gAgentID, LLUUID::null, LLUUID::null);
+	perm.initMasks(PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL);
+
+	// create new script inventory item and wait for it to come back (callback)
+	LLPointer<LLInventoryCallback> callback = new LLParticleScriptCreationCallback(this);
+	create_inventory_item(
+		gAgentID,
+		gAgentSessionID,
+		categoryID,
+		LLTransactionID::tnull,
+		PARTICLE_SCRIPT_NAME,
+		LLStringUtil::null,
+		LLAssetType::AT_LSL_TEXT,
+		LLInventoryType::IT_LSL,
+		NO_INV_SUBTYPE,
+		perm.getMaskNextOwner(),
+		callback);
+
+	setCanClose(FALSE);
+}
+
+void ALFloaterParticleEditor::callbackReturned(const LLUUID& inventoryItemID)
+{
+	setCanClose(TRUE);
+
+	if (inventoryItemID.isNull())
+	{
+		LLNotificationsUtil::add("ParticleScriptCreationFailed");
+		return;
+	}
+
+	mParticleScriptInventoryItem = gInventory.getItem(inventoryItemID);
+	if (!mParticleScriptInventoryItem)
+	{
+		LLNotificationsUtil::add("ParticleScriptNotFound");
+		return;
+	}
+	gInventory.updateItem(mParticleScriptInventoryItem);
+	gInventory.notifyObservers();
+
+	//caps import 
+	const std::string url = gAgent.getRegionCapability("UpdateScriptAgent");
+
+	if (url.empty())
+	{
+		LLNotificationsUtil::add("ParticleScriptFailed");
+		return;
+	}
+
+	
+	const std::string script = createScript();
+
+	LLBufferedAssetUploadInfo::taskUploadFinish_f proc =
+		boost::bind(&ALFloaterParticleEditor::finishUpload, _1, _2, _3, _4, true, mObject->getID());
+	LLResourceUploadInfo::ptr_t uploadInfo(new LLScriptAssetUpload(mObject->getID(), inventoryItemID,
+		LLScriptAssetUpload::MONO, true, LLUUID::null, script, proc));
+	LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
+
+    if (mCloseAfterSave) closeFloater();
+}
+
+// ---------------------------------- Callbacks ----------------------------------
+
+ALFloaterParticleEditor::LLParticleScriptCreationCallback::
+LLParticleScriptCreationCallback(ALFloaterParticleEditor* editor)
+	: mEditor(editor)
+{
+}
+
+void ALFloaterParticleEditor::LLParticleScriptCreationCallback::fire(const LLUUID& inventoryItem)
+{
+	if (!gDisconnected && !LLAppViewer::instance()->quitRequested() && mEditor)
+	{
+		mEditor->callbackReturned(inventoryItem);
+	}
+}
+
+/* static */
+void ALFloaterParticleEditor::finishUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId,
+    LLSD response, bool isRunning, LLUUID objectId)
+{
+    // make sure there's still an object rezzed
+    LLViewerObject* object = gObjectList.findObject(objectId);
+    if (!object || object->isDead())
+    {
+        LL_WARNS("ParticleEditor") << "Failed to inject script in object: " << objectId.asString() << LL_ENDL;
+        return;
+    }
+    auto* script = gInventory.getItem(itemId);
+    object->saveScript(script, TRUE, FALSE);
+
+    LLNotificationsUtil::add("ParticleScriptInjected");
+}
diff --git a/indra/newview/alfloaterparticleeditor.h b/indra/newview/alfloaterparticleeditor.h
new file mode 100644
index 0000000000000000000000000000000000000000..8c856959f5511f80436c7547c9108266ee5b4184
--- /dev/null
+++ b/indra/newview/alfloaterparticleeditor.h
@@ -0,0 +1,150 @@
+/** 
+ * @file alfloaterparticleeditor.h
+ * @brief Particle Editor
+ *
+ * Copyright (C) 2011, Zi Ree @ Second Life
+ * Copyright (C) 2015, Cinder Roxley <cinder@sdf.org>
+ * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef AL_FLOATERPARTICLEEDITOR_H
+#define AL_FLOATERPARTICLEEDITOR_H
+
+#include "llfloater.h"
+#include "llpartdata.h"
+#include "llviewerinventory.h"
+
+class LLButton;
+class LLCheckBoxCtrl;
+class LLColorSwatchCtrl;
+class LLComboBox;
+class LLTextureCtrl;
+class LLUICtrl;
+class LLViewerObject;
+class LLViewerTexture;
+
+class ALFloaterParticleEditor final : public LLFloater
+{
+public:
+	ALFloaterParticleEditor(const LLSD& key);
+	~ALFloaterParticleEditor();
+
+	BOOL postBuild() override;
+    BOOL canClose() override;
+
+	void setObject(LLViewerObject* objectp);
+
+private:
+	void clearParticles();
+	void updateParticles();
+	void updateUI();
+    bool hasChanged() const { return mChanged; }
+    bool handleSaveDialog(const LLSD& notification, const LLSD& response);
+
+	std::string createScript();
+
+	void onParameterChange();
+	void onClickCopy();
+	void injectScript();
+
+	void onClickClearTarget();
+	void onClickTargetPicker();
+	static void onTargetPicked(void* userdata);
+
+	void callbackReturned(const LLUUID& inv_item);
+
+    static std::string lslVector(F32 x, F32 y, F32 z);
+    static std::string lslColor(const LLColor4& color);
+
+    bool mChanged;
+    bool mCloseAfterSave;
+	LLViewerObject* mObject;
+	LLViewerTexture* mTexture;
+	LLViewerInventoryItem* mParticleScriptInventoryItem;
+
+	LLViewerTexture* mDefaultParticleTexture;
+	
+	LLPartSysData mParticles;
+
+	LLComboBox* mPatternTypeCombo;
+	LLTextureCtrl* mTexturePicker;
+
+	LLUICtrl* mBurstRateCtrl;
+	LLUICtrl* mBurstCountCtrl;
+	LLUICtrl* mBurstRadiusCtrl;
+	LLUICtrl* mAngleBeginCtrl;
+	LLUICtrl* mAngleEndCtrl;
+	LLUICtrl* mBurstSpeedMinCtrl;
+	LLUICtrl* mBurstSpeedMaxCtrl;
+	LLUICtrl* mStartAlphaCtrl;
+	LLUICtrl* mEndAlphaCtrl;
+	LLUICtrl* mScaleStartXCtrl;
+	LLUICtrl* mScaleStartYCtrl;
+	LLUICtrl* mScaleEndXCtrl;
+	LLUICtrl* mScaleEndYCtrl;
+	LLUICtrl* mSourceMaxAgeCtrl;
+	LLUICtrl* mParticlesMaxAgeCtrl;
+	LLUICtrl* mStartGlowCtrl;
+	LLUICtrl* mEndGlowCtrl;
+	
+	LLUICtrl* mAcellerationXCtrl;
+	LLUICtrl* mAcellerationYCtrl;
+	LLUICtrl* mAcellerationZCtrl;
+	
+	LLUICtrl* mOmegaXCtrl;
+	LLUICtrl* mOmegaYCtrl;
+	LLUICtrl* mOmegaZCtrl;
+
+	LLComboBox* mBlendFuncSrcCombo;
+	LLComboBox* mBlendFuncDestCombo;
+
+	LLCheckBoxCtrl* mBounceCheckBox;
+	LLCheckBoxCtrl* mEmissiveCheckBox;
+	LLCheckBoxCtrl* mFollowSourceCheckBox;
+	LLCheckBoxCtrl* mFollowVelocityCheckBox;
+	LLCheckBoxCtrl* mInterpolateColorCheckBox;
+	LLCheckBoxCtrl* mInterpolateScaleCheckBox;
+	LLCheckBoxCtrl* mTargetPositionCheckBox;
+	LLCheckBoxCtrl* mTargetLinearCheckBox;
+	LLCheckBoxCtrl* mWindCheckBox;
+	LLCheckBoxCtrl* mRibbonCheckBox;
+
+	LLUICtrl* mTargetKeyInput;
+	LLButton* mClearTargetButton;
+	LLButton* mPickTargetButton;
+	LLButton* mCopyToLSLButton;
+	LLButton* mInjectScriptButton;
+
+	LLColorSwatchCtrl* mStartColorSelector;
+	LLColorSwatchCtrl* mEndColorSelector;
+	
+    static void finishUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning, LLUUID objectId);
+    
+	class LLParticleScriptCreationCallback : public LLInventoryCallback
+	{
+	public:
+		LLParticleScriptCreationCallback(ALFloaterParticleEditor* editor);
+		void fire(const LLUUID& inventoryItem) override;
+		
+	protected:
+		~LLParticleScriptCreationCallback() = default;
+		
+		ALFloaterParticleEditor* mEditor;
+	};
+};
+
+#endif // LL_FLOATERPARTICLEEDITOR_H
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 083ce0856e1431e6be4825ac37bcd9c415ad3208..23bca9df10b69198448fef1f75b4e0132ab797f7 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -30,6 +30,7 @@
 #include "llfloaterreg.h"
 #include "llviewerfloaterreg.h"
 
+#include "alfloaterparticleeditor.h"
 #include "llcommandhandler.h"
 #include "llcompilequeue.h"
 #include "llfasttimerview.h"
@@ -381,6 +382,8 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("world_map", "floater_world_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWorldMap>);	
 
 	// *NOTE: Please keep these alphabetized for easier merges
+
+	LLFloaterReg::add("particle_editor", "floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterParticleEditor>);
 	
 	LLFloaterReg::registerControlVariables(); // Make sure visibility and rect controls get preserved when saving
 }
diff --git a/indra/newview/skins/default/xui/en/floater_particle_editor.xml b/indra/newview/skins/default/xui/en/floater_particle_editor.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f97636029edcd995e9b34a7a0eb52b5efb866971
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_particle_editor.xml
@@ -0,0 +1,876 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_resize="true"
+ height="357"
+ min_height="359"
+ min_width="480"
+ name="particle_editor_floater"
+ save_rect="true"
+ save_visibility="false"
+ single_instance="false"
+ title="PARTICLE EDITOR"
+ width="480">
+  <layout_stack
+   follows="all"
+   animate="false"
+   top="0"
+   height="357"
+   left="0"
+   right="-1"
+   drag_handle_gap="0"
+   drag_handle_first_indent="1"
+   drag_handle_second_indent="1"
+   layout="topleft"
+   name="master_stack"
+   orientation="horizontal"
+   show_drag_handle="true">
+    <layout_panel
+     bg_alpha_color="DkGray2"
+     bg_opaque_color="DkGray2"
+     background_visible="true"
+     background_opaque="true"
+     name="layout_master"
+     follows="all"
+     layout="topleft"
+     auto_resize="false"
+     user_resize="false"
+     height="357"
+     width="300">
+      <accordion
+       left="0"
+       top="0"
+       single_expansion="false"
+       follows="all"
+       layout="topleft"
+       name="accordion_master"
+       height="357"
+       width="300">
+        <accordion_tab
+         expanded="true"
+         layout="topleft"
+         height="152"
+         name="burst_tab"
+         title="Burst/Age"
+         fit_panel="true">
+          <panel
+           follows="all"
+           height="152"
+           left="0"
+           name="burst_panel"
+           top="0"
+        	  right="-1">
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0.3"
+             increment="0.1"
+             name="burst_rate"
+             label="Rate:"
+             label_width="50"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             height="16"
+             max_val="1000"
+             top="0">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="0"
+             initial_value="5"
+             increment="1"
+             name="burst_count"
+             label="Count:"
+             label_width="50"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="1"
+             height="16"
+             max_val="1000"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="burst_radius"
+             label="Radius:"
+             label_width="50"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="50"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="burst_speed_max"
+             label="Speed Max:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="100"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="burst_speed_min"
+             label="Speed Min:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="100"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="source_max_age"
+             label="Source Max Age:"
+             label_width="100"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="1000"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="5"
+             increment="0.001"
+             name="particle_max_age"
+             label="Particle Max Age:"
+             label_width="100"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="30"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+          </panel>
+        </accordion_tab>
+        <accordion_tab
+         expanded="false"
+         layout="topleft"
+         height="140"
+         name="angle_tab"
+         title="Scale/Angle"
+         fit_panel="true">
+          <panel
+           follows="all"
+           height="140"
+           left="0"
+           name="angle_panel"
+           top="0"
+        	  width="313">
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="scale_start_x"
+             label="Scale Start X:"
+             label_width="72"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0.03125"
+             max_val="4"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="scale_start_y"
+             label="Scale Start Y:"
+             label_width="72"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0.03125"
+             max_val="4"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="scale_end_x"
+             label="Scale End Y:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0.03125"
+             max_val="4"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+              function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0.5"
+             increment="0.001"
+             name="scale_end_y"
+             label="Scale End Y:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0.03125"
+             max_val="4"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="angle_begin"
+             label="Angle Begin:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="-3.14"
+             max_val="3.14"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="angle_end"
+             label="Angle End:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="-3.14"
+             max_val="3.14"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+          </panel>
+        </accordion_tab>
+        <accordion_tab
+         expanded="false"
+         layout="topleft"
+         height="100"
+         name="alpha_tab"
+         title="Alpha/Glow"
+         fit_panel="true">
+          <panel
+           follows="all"
+           height="100"
+           left="0"
+           name="alpha_panel"
+           top="0"
+        	  width="313">
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="1"
+             increment="0.001"
+             name="start_alpha"
+             label="Start Alpha:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="1"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="1"
+             increment="0.001"
+             name="end_alpha"
+             label="End Alpha:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="1"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="start_glow"
+             label="Start Glow:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="1"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="3"
+             initial_value="0"
+             increment="0.001"
+             name="end_glow"
+             label="End Glow:"
+             label_width="70"
+             can_edit_text="true"
+             left="0"
+             width="275"
+             min_val="0"
+             max_val="1"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+          </panel>
+        </accordion_tab>
+        <accordion_tab
+         expanded="false"
+         layout="topleft"
+         height="100"
+         name="omega_tab"
+         title="Acelleration/Omega"
+         fit_panel="true">
+          <panel
+           follows="all"
+           height="100"
+           left="0"
+           name="omega_panel"
+           top="0"
+        	  width="313">
+            <text
+             name="Acceleration_Label"
+             follows="left|top"
+             height="16"
+             left="0"
+             top="0"
+             width="76">
+            Acceleration:
+            </text>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="acceleration_x"
+             label="X:"
+             label_width="10"
+             can_edit_text="true"
+             left="0"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="acceleration_y"
+             can_edit_text="true"
+             label="Y:"
+             label_width="10"
+             left_pad="2"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_delta="0">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="acceleration_z"
+             can_edit_text="true"
+             label="Z:"
+             label_width="10"
+             left_pad="2"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_delta="0">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <text
+             name="Omega_Label"
+             follows="left|top"
+             height="16"
+             left="0"
+             top_pad="4"
+             width="76">
+            Omega:
+            </text>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="omega_x"
+             label="X:"
+             label_width="10"
+             can_edit_text="true"
+             left="0"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_pad="2">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="omega_y"
+             label="Y:"
+             label_width="10"
+             can_edit_text="true"
+             left_pad="2"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_delta="0">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+            <slider
+             follows="left|top"
+             decimal_digits="2"
+             initial_value="0"
+             increment="0.01"
+             name="omega_z"
+             label="Z:"
+             label_width="10"
+             can_edit_text="true"
+             left_pad="2"
+             width="90"
+             min_val="-100"
+             max_val="100"
+             height="16"
+             top_delta="0">
+              <slider.commit_callback
+               function="Particle.Edit" />
+            </slider>
+          </panel>
+        </accordion_tab>
+        <accordion_tab
+         expanded="true"
+         layout="topleft"
+         height="100"
+         name="color_tab"
+         title="Color/Texture"
+         fit_panel="true">
+          <panel
+           follows="all"
+           height="100"
+           left="0"
+           name="color_panel"
+           top="0"
+        	  width="313">
+            <texture_picker
+             follows="left|top"
+             height="70"
+             width="62"
+             left="0"
+             can_apply_immediately="true"
+             name="texture_picker"
+             label="Texture"
+             top="4">
+              <texture_picker.commit_callback
+               function="Particle.Edit" />
+            </texture_picker>
+            <color_swatch
+             follows="left|top"
+             height="70"
+             width="62"
+             label="Start Color"
+             left_pad="5"
+             can_apply_immediately="true"
+             name="start_color_selector"
+             top_delta="0">
+              <color_swatch.commit_callback
+               function="Particle.Edit" />
+            </color_swatch>
+            <color_swatch
+             follows="left|top"
+             height="70"
+             width="62"
+             label="End Color"
+             left_pad="5"
+             name="end_color_selector"
+             can_apply_immediately="true"
+             top_delta="0">
+              <color_swatch.commit_callback
+               function="Particle.Edit" />
+            </color_swatch>
+            <combo_box
+             name="blend_func_src_combo"
+             follows="left|top"
+            	height="22"
+            	left_pad="4"
+            	top_delta="0"
+            	width="74">
+              <combo_box.commit_callback
+               function="Particle.Edit" />
+             	<combo_box.item
+               label="Blend One"
+               name="blend_one"
+             		value="blend_one" />
+             	<combo_box.item
+             		label="Blend Zero"
+             		name="blend_zero"
+             		value="blend_zero" />
+             	<combo_box.item
+             		label="Blend Dest Color"
+             		name="blend_dest_color"
+             		value="blend_dest_color" />
+             	<combo_box.item
+             		label="Blend Src Color"
+             		name="blend_src_color"
+             		value="blend_src_color" />
+             	<combo_box.item
+             		label="Blend 1 - Dest Color"
+             		name="blend_one_minus_dest_color"
+             		value="blend_one_minus_dest_color" />
+             	<combo_box.item
+             		label="Blend 1 - Src Color"
+             		name="blend_one_minus_src_color"
+             		value="blend_one_minus_src_color" />
+             	<combo_box.item
+             		label="Blend Src Alpha"
+             		name="blend_src_alpha"
+             		value="blend_src_alpha" />
+             	<combo_box.item
+             		label="Blend 1 - Src Alpha"
+             		name="blend_one_minus_src_alpha"
+             		value="blend_one_minus_src_alpha" />
+            </combo_box>
+            <combo_box
+             name="blend_func_dest_combo"
+             follows="left|top"
+             height="22"
+             left_delta="0"
+             top_pad="4"
+             width="74">
+              <combo_box.commit_callback
+               function="Particle.Edit" />
+             	<combo_box.item
+             		label="Blend One"
+             		name="blend_one"
+             		value="blend_one" />
+             	<combo_box.item
+             		label="Blend Zero"
+             		name="blend_zero"
+             		value="blend_zero" />
+             	<combo_box.item
+             		label="Blend Dest Color"
+             		name="blend_dest_color"
+             		value="blend_dest_color" />
+             	<combo_box.item
+             		label="Blend Src Color"
+             		name="blend_src_color"
+             		value="blend_src_color" />
+             	<combo_box.item
+             		label="Blend 1 - Dest Color"
+             		name="blend_one_minus_dest_color"
+             		value="blend_one_minus_dest_color" />
+             	<combo_box.item
+             		label="Blend 1 - Src Color"
+             		name="blend_one_minus_src_color"
+             		value="blend_one_minus_src_color" />
+             	<combo_box.item
+             		label="Blend Src Alpha"
+             		name="blend_src_alpha"
+             		value="blend_src_alpha" />
+             	<combo_box.item
+             		label="Blend 1 - Src Alpha"
+             		name="blend_one_minus_src_alpha"
+             		value="blend_one_minus_src_alpha" />
+             </combo_box>
+            </panel>
+          </accordion_tab>
+        </accordion>
+      </layout_panel>
+      <layout_panel
+       layout="topleft"
+      	follows="all"
+      	height="258"
+      	left_pad="0"
+      	name="checkbox_panel"
+      	width="120">
+      <text
+       name="Pattern_Label"
+	      follows="left|top"
+      	height="16"
+      	left="0"
+      	top="0"
+      	width="100">
+      Pattern:
+      </text>
+      <combo_box
+      	follows="left|right|top"
+      	name="pattern_type_combo"
+      	top_pad="2"
+      	left_delta="0"
+       right="-2">
+        <combo_box.commit_callback
+         function="Particle.Edit" />
+        <combo_box.item
+         label="Explode"
+         name="explode"
+         value="explode" />
+        <combo_box.item
+         label="Angle"
+         name="angle"
+         value="angle" />
+       	<combo_box.item
+      		 label="Angle Cone"
+      	 	name="angle_cone"
+       		value="angle_cone" />
+       	<combo_box.item
+      		 label="Angle Cone Empty"
+      	 	name="angle_cone_empty"
+       		value="angle_cone_empty" />
+      	 <combo_box.item
+      		 label="Drop"
+      	 	name="drop"
+       		value="drop" />
+      </combo_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="bounce_checkbox"
+      	label="Bounce"
+      	left="0"
+      	top_pad="4"
+      	width="80">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="emissive_checkbox"
+      	label="Emissive"
+      	top_pad="4"
+       left="0"
+      	width="80">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="wind_checkbox"
+      	label="Wind"
+      	top_pad="4"
+       left="0"
+      	width="80">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="ribbon_checkbox"
+      	label="Ribbon"
+      	top_pad="4"
+       left="0"
+      	width="80">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="follow_source_checkbox"
+      	label="Follow Source"
+      	top_pad="4"
+       left="0"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="follow_velocity_checkbox"
+      	label="Follow Velocity"
+      	top_pad="4"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="interpolate_color_checkbox"
+      	label="Interpolate Color"
+      	top_pad="4"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+    	  follows="left|top"
+      	height="16"
+      	name="interpolate_scale_checkbox"
+      	label="Interpolate Scale"
+      	top_pad="4"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="target_position_checkbox"
+      	label="Target Position"
+      	top_pad="4"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <check_box
+      	follows="left|top"
+      	height="16"
+      	name="target_linear_checkbox"
+      	label="Target Linear"
+      	top_pad="4"
+      	width="120">
+        <check_box.commit_callback
+         function="Particle.Edit" />
+      </check_box>
+      <line_editor
+       follows="left|right|top"
+       left="0"
+       name="target_key_input"
+       height="20"
+       right="-48">
+        <line_editor.commit_callback
+         function="Particle.Edit" />
+      </line_editor>
+      <button
+       follows="right|top"
+       image_overlay="StopReload_Over"
+       left_pad="4"
+       name="clear_target_button"
+       tool_tip="Clear target object or avatar"
+       width="20"
+       height="20" />
+      <button
+       follows="right|top"
+       image_overlay="Inv_Object"
+       left_pad="2"
+       name="pick_target_button"
+       tool_tip="Click here to select the particle target object or avatar."
+       width="20"
+       height="20" />
+      <button
+       follows="left|top"
+      	height="24"
+      	name="copy_button"
+      	label="Copy"
+      	tool_tip="Copies the particle system's parameter as LSL script to the clipboard."
+      	top_pad="7"
+       left="2"
+      	width="85" />
+      <button
+      	follows="left|top"
+      	height="24"
+      	name="inject_button"
+      	label="Inject"
+      	tool_tip="Inject this particle system into the selected object."
+      	left_pad="4"
+       width="85" />
+    </layout_panel>
+  </layout_stack>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_object.xml b/indra/newview/skins/default/xui/en/menu_object.xml
index ce34508303b34122fdf9ea5e4e8ee04f1791c7f4..0a895faa57365964c52fd2bbf7d3b78a6dc1201a 100644
--- a/indra/newview/skins/default/xui/en/menu_object.xml
+++ b/indra/newview/skins/default/xui/en/menu_object.xml
@@ -29,6 +29,16 @@
     <menu_item_call.on_enable
         function="EnableEdit"/>
   </menu_item_call>
+    <menu_item_call
+     label="Edit Particles"
+     name="Menu Object Edit Particles">
+      <menu_item_call.on_click
+       function="Object.EditParticles" />
+      <menu_item_call.on_enable
+       function="Object.EnableEditParticles" />
+      <menu_item_call.on_visible
+       function="Object.EnableEditParticles" />
+    </menu_item_call>
   <menu_item_call
       enabled="false"
       label="Open"
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index a8aaf26fec3223671aab4066263de56151fff0dc..d8d94ffaf00e724e2845e61198a353f6b1af7496 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -1034,6 +1034,14 @@ function="World.EnvPreset"
 			<menu_item_call.on_enable
                function="Tools.EnableTakeCopy" />
           </menu_item_call>
+          <menu_item_call
+             label="Edit Particles"
+             name="Menu Object Edit Particles">
+            <menu_item_call.on_click
+               function="Object.EditParticles" />
+            <menu_item_call.on_enable
+               function="Object.EnableEditParticles" />
+          </menu_item_call>
           <menu_item_call
 			 label="Save Back to Object Contents"
 			 name="Save Object Back to Object Contents">
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 1febca05611df06d847c86e8f450bc6f16c35707..9a3daf526517f1e0a786e438255f96d2bc379702 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -11748,5 +11748,64 @@ Unable to load the track from [TRACK1] into [TRACK2].
     Auto-accepted [ITEM] from [NAME] and placed in inventory.
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   name="ParticleSaveChanges"
+   type="alertmodal">
+Inject with particle script before closing?
+   <tag>confirm</tag>
+   <usetemplate
+    canceltext="Cancel"
+    name="yesnocancelbuttons"
+    notext="Don&apos;t Inject"
+    yestext="Inject"/>
+  </notification>
+    
+  <notification
+   icon="alertmodal.tga"
+   name="ParticleScriptFindFolderFailed"
+   type="alertmodal">
+Could not find a folder for the new script in inventory.
+  </notification>
+
+  <notification
+   icon="alertmodal.tga"
+   name="ParticleScriptCreationFailed"
+   type="alertmodal">
+Could not create new script for this particle system.
+  </notification>
+
+  <notification
+   icon="alertmodal.tga"
+   name="ParticleScriptNotFound"
+   type="alertmodal">
+Could not find the newly created script for this particle system.
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="ParticleScriptInjected"
+   type="notify">
+Particle script was injected successfully.
+  </notification>
+
+  <notification
+   icon="alertmodal.tga"
+   name="ParticleScriptFailed"
+   type="alertmodal">
+Failed to inject script into object.
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="ParticleScriptCopiedToClipboard"
+   type="notify">
+The LSL script to create this particle system has been copied to your clipboard. You can now paste it into a new script to use it.
+   <form name="form">
+      <ignore name="ignore"
+       text="A particle script was copied to my clipboard"/>
+    </form>
+  </notification>
+
   
 </notifications>