Jump to content

The official unoffical "help a fellow plugin developer" thread


Recommended Posts

Anybody have any idea why using "KSPAddon.Startup.EditorAny" only works for the VAB and not for the SPH? I know I can just do one for each, but using EditorAny seems like a much nicer approach...

EDIT: actually this might not be EditorAny giving me problems, but GameScenes. I'm just not getting things to show up in the SPH that are working just fine in the VAB =P

Found out the problem, and it was indeed the GameScenes. I had to include both GameScenes.EDITOR AND GameScenes.SPH. I hope Squad fixes this in the future, having "Editor" and "SPH" are a bit inconsistent; definitely needs "VAB" added and "Editor" mean either scene.

Ekku: Have you tried using KSPAddonFixed by Majiir? Probably won't change anything but it's what all the ~cool~ modders are using. If the editor thing doesn't work have you considered loading at the Menu? Hyperedit does that.

Thanks for the advice, I took a look at that previously and felt that I was fine just using KSPAddon with the "once" parameter set to false. It works for me currently, and it's what Kerbal Alarm Clock uses (which my mod is quite similar to in certain ways), so I'll stick to that unless something comes up later =3

Link to comment
Share on other sites

It should be simple, but I've tried a bunch of functions and variables within moduleengines and partmodule without any success.

We know it's KSPEvent tagged because it appears in the GUI, so as per sticky you use partInQuestion.SendEvent("EventName");

As to getting the event name, try iterating through PartModule.Events and seeing what name corresponds to the GUIName of the right click menu (alternative just guess, they're usually something simple and similar like Shutdown or ShutdownEngine).

Link to comment
Share on other sites

We know it's KSPEvent tagged because it appears in the GUI, so as per sticky you use partInQuestion.SendEvent("EventName");

As to getting the event name, try iterating through PartModule.Events and seeing what name corresponds to the GUIName of the right click menu (alternative just guess, they're usually something simple and similar like Shutdown or ShutdownEngine).

part.SendEvent("Shutdown"); causes all engines on the vessel to shut down. In the documentation I could find, it looks like SendEvent should only be sending the command to the part, but that doesn't appear to be what happens.

Link to comment
Share on other sites

part.SendEvent("Shutdown"); causes all engines on the vessel to shut down. In the documentation I could find, it looks like SendEvent should only be sending the command to the part, but that doesn't appear to be what happens.

another thing you could do is loop through all the part that have your module and tell them to shutdown.

Something like this:


foreach (Part p in vessel.parts)
{
foreach (PartModule pm in p.Modules)
{
if (pm is Landertron) { p.SendEven("Shutdown"); }
}
}

Edited by stupid_chris
Link to comment
Share on other sites

Does GUI.Tooltip not work? It's always blank for me. I'm calling it from OnGUI() and checking the event type, like the example:

if (Event.current.type == EventType.Repaint) {

tooltip = GUI.tooltip;

}

I'm fairly certain I'm declaring my tooltips correctly:

if (GUILayout.Button(new GUIContent ("Asparagus",aspTexture, "Create fuel lines and stage the ship, asparagus-style"),picbuttonstyle)) {

mystate = ASPState.ADDASP;

}

I can whip up my own detection of what control I'm over, but it seems silly that I would have to do so.

Also is there any good way to shrink a window to the current content? The best I've come up with is to change the height and width to 1x1 and let it automatically resize, but that causes flicker.

Link to comment
Share on other sites

Does GUI.Tooltip not work? It's always blank for me. I'm calling it from OnGUI() and checking the event type, like the example:

I'm fairly certain I'm declaring my tooltips correctly:

I can whip up my own detection of what control I'm over, but it seems silly that I would have to do so.

Also is there any good way to shrink a window to the current content? The best I've come up with is to change the height and width to 1x1 and let it automatically resize, but that causes flicker.

Your code snippets certainly look right. A few things that I can think of when I did this recently (this is just my fiddling :) ):

  • I didnt do it in the OnGUI routine itself - I added my draw functions to the renderingmanager - RenderingManager.AddToPostDrawQueue(0, DrawGUI)
  • In my DrawGUI function that is where i drew my window with GUILayout.Window
  • Inside the window function is where I have all my GUILayout.button, etc
  • The last thing in that window function is the bit that reads the GUI.tooltip
  • Then you obviously do stuff with the tooltip string - in my case I draw the tooltip on the screen as the last thing in that DrawGUI routine

Hope thats helpful, and if you want to see it in use you can see the functions/order I use in https://kspalternateresourcepanel.codeplex.com/SourceControl/latest#DevBranch/Source/AlternateResourcePanel.cs

For the autofit content I have yet to find something in the native unity stuff myself

Link to comment
Share on other sites

  • The last thing in that window function is the bit that reads the GUI.tooltip
  • Then you obviously do stuff with the tooltip string - in my case I draw the tooltip on the screen as the last thing in that DrawGUI routine

Hope thats helpful, and if you want to see it in use you can see the functions/order I use in https://kspalternateresourcepanel.codeplex.com/SourceControl/latest#DevBranch/Source/AlternateResourcePanel.cs

That was exactly what I needed, thanks! Now I read the tooltip at the end of OnWindow() for the window, and then draw the tooltip after the GUILayout.Window() call in OnGUI(). Also set GUI.Depth=0. It works!

I noticed you used a real texture file for the background, but I just made it black for now:


Texture2D blackTexture = new Texture2D (1, 1);
blackTexture.SetPixel(0,0,Color.black);
blackTexture.Apply();
tooltipstyle.normal.background = blackTexture;

Link to comment
Share on other sites

Any idea of how I can bar certain experiments that come from the same part until a certain tech node is researched?

I'm just figuring that out too. Here's what I've come up with so far:


if (ResearchAndDevelopment.Instance != null) {

if (ResearchAndDevelopment.PartModelPurchased (availablePart)) {
}

if (ResearchAndDevelopment.GetTechnologyState ("techIDstring") == RDTech.State.Available) {
}
}

Link to comment
Share on other sites

I'm just figuring that out too. Here's what I've come up with so far:


if (ResearchAndDevelopment.Instance != null) {

if (ResearchAndDevelopment.PartModelPurchased (availablePart)) {
}

if (ResearchAndDevelopment.GetTechnologyState ("techIDstring") == RDTech.State.Available) {
}
}

So then it would look something like this:


if (ResearchAndDevelopment.Instance != null) {

if (ResearchAndDevelopment.PartModelPurchased ([COLOR="#FF0000"]Stayputnik's ID[/COLOR])) {[COLOR="#FFA500"]DO STUFF![/COLOR]
}

if (ResearchAndDevelopment.GetTechnologyState ("[COLOR="#FF0000"]spaceExploration[/COLOR]") == RDTech.State.Available) { [COLOR="#FFA500"]add the new experiment[/COLOR]
}
}

... Right?

Link to comment
Share on other sites

Something that people might find useful: There's a new float in ModuleEngines called thrustPercentage. This is a value from 0 to 100 that seems to correspond to where you set the thrust limiter in the new VAB tweakable for engines.

And a question: Any suggestions on how to find all copies of a part that are tied to the same symmetry? I tried the following without success

        foreach (Part p in this.vessel.parts)
{
if (p.isSymmetryCounterPart(this.part))
{
...
}
}

Edited by XanderTek
Link to comment
Share on other sites

So then it would look something like this:


if (ResearchAndDevelopment.Instance != null) {

if (ResearchAndDevelopment.PartModelPurchased ([COLOR="#FF0000"]Stayputnik's ID[/COLOR])) {[COLOR="#FFA500"]DO STUFF![/COLOR]
}

if (ResearchAndDevelopment.GetTechnologyState ("[COLOR="#FF0000"]spaceExploration[/COLOR]") == RDTech.State.Available) { [COLOR="#FFA500"]add the new experiment[/COLOR]
}
}

... Right?

Close. PartModelPurchased() needs an AvailablePart, like:


AvailablePart ap = PartLoader.getPartInfoByName ("fuelLine");
if (ResearchAndDevelopment.PartTechAvailable(ap)) {
}

The name (in this case "fuelLine") is the part name in the part.cfg file.

You probably don't need to check both the tech tree and the part availability, just one or the other.

And a question: Any suggestions on how to find all copies of a part that are tied to the same symmetry?

Something like this:


foreach (Part p in this.vessel.parts){
if (p.symmetryMode>0) {
foreach (Part brother in p.symmetryCounterparts) {
}
}
}

Link to comment
Share on other sites

Close. PartModelPurchased() needs an AvailablePart, like:


AvailablePart ap = PartLoader.getPartInfoByName ("fuelLine");
if (ResearchAndDevelopment.PartTechAvailable(ap)) {
}

The name (in this case "fuelLine") is the part name in the part.cfg file.

Sooo... Then:


if (ResearchAndDevelopment.Instance != null) {
AvailablePart ap = PartLoader.getPartInfoByName ("[COLOR="#FF0000"]Stayputnik's ID[/COLOR]");
if (ResearchAndDevelopment.PartTechAvailable(ap)) {

if (ResearchAndDevelopment.PartModelPurchased ([COLOR="#FF0000"]Stayputnik's ID[/COLOR])) {[COLOR="#FF8C00"]DO STUFF![/COLOR]
}

if (ResearchAndDevelopment.GetTechnologyState ("[COLOR="#FF0000"]spaceExploration[/COLOR]") == RDTech.State.Available) {[COLOR="#FF8C00"] add the new experiment[/COLOR]
}
}
}

...?

I'm really bad at figuring code...

Link to comment
Share on other sites

Thanks guys!

I ended up successfully using this to find each part that has symmetry with the current one (useful for making your tweakables functions affect all parts in a symmetry):

            foreach (Part p in this.part.symmetryCounterparts)
{
...
}

For some reason, p.symmetryMode was not greater than zero in every case. Maybe the "master" part had p.symmetryMode=0? In any case, I was able to just omit that line.

And I used this to successfully shut down only the current engine:

part.SendMessage("Shutdown");

Link to comment
Share on other sites

From the error message it may be a scope issue, can you post your code?

specifically the following code compiles fine for me:

public class SomeClass : PartModule
{
[KSPField]
public float color_red = 1.0f;
[KSPField]
public float color_green = 1.0f;
[KSPField]
public float color_blue = 1.0f;

public Color tracerColor;

public override void OnStart(StartState state)
{
tracerColor = new Color(color_red, color_green, color_blue);
}
}

i dont know what on earth i did but i fixed it

thx for your effort guys, even if i dont really know what on earth i did to make it work

finally have tracers defined in each part file, so i can shoot rainbows (or at least make the sci-fi gunpods shoot blue/purple, conventional ones shoot red/orange/yellow/white)

now i have another question, how can i make a random vector modifier?

i have a vector that defines the direction the bullet is going, but i want to be able to add some random spread (preferable configurable by a modifier that can be made to work.

***How can i implement random bullet spread in this code?***

also,

***How can i modify bullet initial velocity?***

here is what i have sofar (based on DYJ's minigun code, modified to be used with my upcoming gunpods and missile pack mod release):



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace AKSKineticEnergyWeapon
{
public class AKSKineticEnergyWeapon : PartModule
{
[KSPField]
public string keyFire = "joystick button 0";
[KSPField]
public string keyFirealt = "[0]";
[KSPField]
public float BulletMass = 0.5f;
[KSPField]
public float BulletDrag = 0.05f;
[KSPField]
public float BulletPower = 120f;
[KSPField]
public float recoilreductionfactor = 0.1f;
[KSPField]
public float fireRate = 0.08f;
[KSPField]
public float lackofkrakensbanontrailrendererscompensation = 0.2f;

public string test;

public FXGroup sound = null;

[KSPField]
public string soundfilelocation = "AKSTechnologies/sounds/20mm";

private float nextFire = 0.0f;

public const string quote = "\"";

[KSPField]
public float rotationSpeed = 1200f;
[KSPField]
public bool isGatling = false;
[KSPField]
public float heatProduction = 800;

[KSPField]
public string ProjectileFileLoc;

[KSPField]
public string CasingFileLoc;

private VInfoBox heatGauge = null;

[KSPField]
public string ProjectileType;

[KSPField]
public bool CasingCollision = false;



[KSPField]
public string Casinglocationwithquotes;
[KSPField]
public float color_red = 1.0f;
[KSPField]
public float color_green = 0.0f;
[KSPField]
public float color_blue = 0.0f;

public Color tracerColor;


public GameObject Bullet, Casing, Muzzlefx, Rocketfx;

public override void OnStart(PartModule.StartState state)
{

if (!Bullet)
{
//Bullet = PartReader.Read(KSPUtil.ApplicationRootPath + ProjectileFileLoc, "model", ".mu");
Bullet = GameDatabase.Instance.GetModel("AKSTechnologies/Parts/bulletcasing/Bullet/model");
Bullet.name = "Bullet";
Bullet.transform.position = new Vector3(1e10f, 1e10f, 1e10f);
Bullet.SetActive(true);

if (ProjectileType == "bullet")
{
Casing = GameDatabase.Instance.GetModel("AKSTechnologies/Parts/bulletcasing/Casing/model");
Casing.name = "Casing";
Casing.transform.position = new Vector3(1e10f, 1e10f, 1e10f);
Casing.SetActive(true);
}
}

Muzzlefx = (GameObject)GameObject.Instantiate(UnityEngine.Resources.Load("Effects/fx_exhaustFlame_yellow"));
Muzzlefx.particleEmitter.emit = false;
Muzzlefx.transform.parent = (transform.Find("model/MuzzleLoc"));
Muzzlefx.transform.localPosition = new Vector3(0, -0.5F, 0);
Muzzlefx.transform.localRotation = this.part.transform.rotation;
Muzzlefx.particleEmitter.useWorldSpace = false;
Muzzlefx.particleEmitter.minSize = Muzzlefx.particleEmitter.maxSize = 0.75f;
Muzzlefx.particleEmitter.localVelocity = Vector3.zero;

sound.audio = gameObject.AddComponent<AudioSource>();
sound.audio.volume = GameSettings.SHIP_VOLUME;
sound.audio.maxDistance = 10;
sound.audio.Stop();
sound.audio.clip = GameDatabase.Instance.GetAudioClip("AKSTechnologies/Sounds/20mm");
sound.audio.loop = true;



//tracer color defenition
tracerColor = new Color(color_red, color_green, color_blue);

}







public void Update()
{


if (!HighLogic.LoadedSceneIsFlight) return;

if (vessel == null)
{
return;
}



if (this.part.temperature > this.part.maxTemp * 0.3F)
{
if (heatGauge == null)
heatGauge = InitHeatGauge(this.part);
heatGauge.SetValue(this.part.temperature, this.part.maxTemp, this.part.maxTemp);
}

else if (heatGauge != null)
{
this.part.stackIcon.ClearInfoBoxes();
heatGauge = null;
}

//sound playing additional information
if ((Input.GetKeyDown(keyFire) == true || Input.GetKeyDown(keyFirealt)) && (vessel.isActiveVessel == true)) sound.audio.Play();
if ((Input.GetKeyUp(keyFire) == true || Input.GetKeyUp(keyFirealt)) && (vessel.isActiveVessel == true)) sound.audio.Stop();

if ((Input.GetKey(keyFire) || Input.GetKey(keyFirealt)) && (Time.time > nextFire) && (vessel.isActiveVessel == true))
{
nextFire = Time.time + fireRate;

GameObject newBullet = (GameObject)GameObject.Instantiate(Bullet);

this.part.transform.rigidbody.AddForce(-part.transform.up * BulletPower * recoilreductionfactor, ForceMode.Impulse);



Vector3d orbitalVelocity = vessel.orbit.GetVel();
Vector3d airVelocity = vessel.mainBody.getRFrmVel(vessel.findWorldCenterOfMass());
Vector3d relativeVelocity = ((orbitalVelocity - airVelocity) * lackofkrakensbanontrailrendererscompensation);

if (Krakensbane.GetFrameVelocityV3f() == Vector3.zero)
{
newBullet.transform.position = (transform.Find("model/MuzzleLoc").position);
}
else
{
newBullet.transform.position = (transform.Find("model/MuzzleLoc").position);
}







if (ProjectileType == "bullet")
{

newBullet.transform.rotation = part.transform.rotation;
newBullet.AddComponent<Rigidbody>();
newBullet.rigidbody.mass = BulletMass;
newBullet.rigidbody.drag = BulletDrag;

Muzzlefx.particleEmitter.emit = true;

newBullet.AddComponent<Hit>();
newBullet.AddComponent<Bulleteffects>();


/* casing spawn code, disabled for now as current gunpod 3d models lack shell ejection ports and look weird with random shells spawning
GameObject newCasing = (GameObject)GameObject.Instantiate(Casing);

newCasing.transform.position = transform.Find("model/CasingLoc").position;
newCasing.AddComponent<Rigidbody>();
newCasing.transform.rotation = UnityEngine.Random.rotation;
newCasing.rigidbody.velocity = (this.vessel.rigidbody.velocity);
newCasing.rigidbody.AddForce(part.transform.forward * 2, ForceMode.Impulse);
newCasing.rigidbody.drag = 0.9f;
newCasing.rigidbody.detectCollisions = false;

if (CasingCollision == true)
{
newCasing.AddComponent<SphereCollider>();
}
newCasing.AddComponent<CasingCleanup>();
*/

newBullet.rigidbody.velocity = this.vessel.rigidbody.velocity;
newBullet.rigidbody.AddForce(part.transform.up * BulletPower, ForceMode.Impulse);
newBullet.rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
newBullet.AddComponent<physicalObject>();


}
/* unused code leftover from DYJ's original, keeping incase rocket launcher pods are implemented

if (ProjectileType == "rocket")
{

newBullet.transform.rotation = part.transform.rotation;
newBullet.AddComponent<Rigidbody>();
newBullet.AddComponent<physicalObject>();
newBullet.rigidbody.mass = BulletMass;


newBullet.rigidbody.drag = BulletDrag;
//print("Bulletcreated");

newBullet.AddComponent<ExplosiveHit>();
newBullet.AddComponent<Rocketeffects>();

newBullet.rigidbody.velocity = this.vessel.rigidbody.velocity;
newBullet.rigidbody.AddForce(part.transform.up * BulletPower, ForceMode.Force);
newBullet.rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
}
*/
}

else
{
Muzzlefx.particleEmitter.emit = false;
}





if ((Input.GetKey(keyFire) || Input.GetKey(keyFirealt)) && (vessel.isActiveVessel == true))
{

this.rigidbody.AddForce(part.transform.up * (-BulletPower * recoilreductionfactor), ForceMode.Force);
part.temperature += heatProduction * TimeWarp.deltaTime;

/* unused currently, will be reused later once models get animated moving shell feed belting
if (isGatling == true)
{
Transform Barrels = base.transform.FindChild("model").FindChild("Base").FindChild("Barrelparent");
Barrels.transform.Rotate(Vector3.forward * (rotationSpeed * TimeWarp.deltaTime));
}
*/
}
}

static public VInfoBox InitHeatGauge(Part p)
{
VInfoBox v = p.stackIcon.DisplayInfo();

v.SetMsgBgColor(XKCDColors.DarkRed);
v.SetMsgTextColor(XKCDColors.Orange);
v.SetMessage("Overheat");
v.SetProgressBarBgColor(XKCDColors.DarkRed);
v.SetProgressBarColor(XKCDColors.Orange);

return v;
}
}



public class CasingCleanup : MonoBehaviour
{
public float casingTimeout = 1.5f;

void Awake()
{

Destroy(this.gameObject, (casingTimeout));

}

}

public class Hit : MonoBehaviour
{
public void OnCollisionEnter(Collision collision)
{
Destroy(this.gameObject, 0.1f);
//print("Hit!");
}

}

public class ExplosiveHit : MonoBehaviour
{
public void OnCollisionEnter(Collision collision)
{
Destroy(this.gameObject, 0.1f);
//print("Hit!");

}

}

public class Bulleteffects : MonoBehaviour
{

public float bulletTimeout = 5.0f;
public Shader shader1 = Shader.Find("Transparent/VertexLit");
public Color tracerColor;


void Awake()
{



this.gameObject.AddComponent<TrailRenderer>();
TrailRenderer trailRenderer = (TrailRenderer)this.gameObject.renderer;
trailRenderer.time = 0.2f;
trailRenderer.startWidth = 0.15f;
trailRenderer.endWidth = 0.0f;
trailRenderer.autodestruct = true;



trailRenderer.material.shader = shader1;
trailRenderer.material.SetColor("_Emission", tracerColor);
trailRenderer.material.SetColor("_Color", tracerColor);


Destroy(this.gameObject, (bulletTimeout));

this.gameObject.AddComponent<Light>();
this.gameObject.light.type = LightType.Point;
this.gameObject.light.color = tracerColor;
this.gameObject.light.range = 5f;
this.gameObject.light.intensity = 0.5F;
this.gameObject.light.renderMode = LightRenderMode.ForcePixel;
}

}

public class Rocketeffects : MonoBehaviour //unused for now, will be needed once rocket pod implemented
{

public float bulletTimeout = 5.0f;
public GameObject Rocketfx;
[KSPField]
public float RocketPower = 10f;

void Awake()
{


Rocketfx = (GameObject)GameObject.Instantiate(UnityEngine.Resources.Load("Effects/fx_exhaustFlame_blue"));
Rocketfx.particleEmitter.emit = true;
Rocketfx.transform.parent = (transform.Find("model/FFarRocket"));
Rocketfx.transform.localPosition = new Vector3(0, -0.5F, 0);
Rocketfx.particleEmitter.useWorldSpace = false;
print(transform.Find("model/FFarRocket"));


Destroy(this.gameObject, (bulletTimeout));

this.gameObject.AddComponent<Light>();
this.gameObject.light.type = LightType.Point;
this.gameObject.light.color = Color.red;
this.gameObject.light.range = 25f;
this.gameObject.light.intensity = 0.5F;
this.gameObject.light.renderMode = LightRenderMode.ForcePixel;
}

}
}

now i know basic C# coding, but as to how im going to make variable initial velocity (need that since a 30mm snub nose cannon is obviously not the same as a 20mm high velocity long barrel gun), and also variable random spread (again, 30mm less accurate then 20mm in this case, so need to balance the 30mm firepower boost with spread)

Link to comment
Share on other sites

Hmm... Any idea where I can find the abilities a Kerbal has while on EVA? As in the file(s) that govern the EVA...

There is a KerbalEVA partModule that configures a lot of stuff you might use. I don't think its properly saved when a kerbal enters a vessel, so you might have to work around that yourself. Haven't found any related CFGs, if that's what you were looking for.

Link to comment
Share on other sites

There is a KerbalEVA partModule that configures a lot of stuff you might use. I don't think its properly saved when a kerbal enters a vessel, so you might have to work around that yourself. Haven't found any related CFGs, if that's what you were looking for.

Ah, and where would one find said partModule, pray tell?

Also, regarding my ideas for the Cabana Corp. Data Drives- would it be possible to have the module for the Data Drives inherit from the ModuleScienceContainer?

I'm looking for it to have a function similar to the "transmission boost" of the Space Lab as well as being able to retrieve data from experiments on the same vessel automatically -results will be stored on the Drive upon completion of the experiment, instead of on the equipment.

Link to comment
Share on other sites

is there any way to get the part a module is attached to?

in my case... if I add a module to an engine using Module Manager, is there a way to access from the new module the methods and variables of the ModuleEngines of the same part?

Link to comment
Share on other sites

I started fumbling around with modding just today, so there might be an easy answer for this.

I really would like to run my code after the player recovers a vessel. I don't care if it runs before the science calculations, or right after it. I tried using the OnInactive() method of a part, but it seems that's only gets called when the part blows up. I checked, and can't find anything recovery related neither around the vessel or the staging API. It probably wouldn't be impossible for me to work around this, and try to figure out if a recovery just happened when the player reaches the spacecenter view, but I'd rather not do that if not necessary.

Link to comment
Share on other sites

I started fumbling around with modding just today, so there might be an easy answer for this.

I really would like to run my code after the player recovers a vessel. I don't care if it runs before the science calculations, or right after it. I tried using the OnInactive() method of a part, but it seems that's only gets called when the part blows up. I checked, and can't find anything recovery related neither around the vessel or the staging API. It probably wouldn't be impossible for me to work around this, and try to figure out if a recovery just happened when the player reaches the spacecenter view, but I'd rather not do that if not necessary.

There is a big static class GameEvents full of good stuff. Try OnVesselRecovered and OnVesselRecoveryRequested. Good luck.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...