Trigger creating help (plugin development)Solved

Hey

I am fairly new here, but the idea is basicly to create a small trigger around every public quarry to post a message to the player triggering it (using the ShowToast method), so I would like to know how do I spawn in this trigger, i know my way around in C# but not in rust plugin development (started this since last week). I tried googling around but cant seem to find anything useful.
I also looked into some other plugins but whatever I try from what I see, it just wont work out.

Any help is appriciated, if I just know how to spawn them in on their location and make them work, I should be good to go.
Thanks in advance!

I found out how to sorta spawn them, but now I am stuck in sizing... How do you even resize a trigger to fit around a public quarry?
Is there anyone please who can give me a little help here please, thanks in advance

Create a game object. To position it, move its transform.

Add a collider component to the game object, such as a BoxCollider or SphereCollider. Set the size or radius of the collider. Set the isTrigger property on the collider to true.

For collision detection, either attach a custom component with Unity methods like OnTriggerEnter, OnTriggerExit, OnTriggerStay; or attach a Rust component like TriggerBase (or subclass) and use Oxide hooks to detect collision, or override methods of that trigger for detection.

To verify that you have positioned and sized the collider correctly, send the player a console command such as ddraw.box or ddraw.sphere with appropriate arguments to visual the collider in game.

NKXTQs24ExGTuL8.jpg WhiteThunder

Create a game object. To position it, move its transform.

Add a collider component to the game object, such as a BoxCollider or SphereCollider. Set the size or radius of the collider. Set the isTrigger property on the collider to true.

For collision detection, either attach a custom component with Unity methods like OnTriggerEnter, OnTriggerExit, OnTriggerStay; or attach a Rust component like TriggerBase (or subclass) and use Oxide hooks to detect collision, or override methods of that trigger for detection.

To verify that you have positioned and sized the collider correctly, send the player a console command such as ddraw.box or ddraw.sphere with appropriate arguments to visual the collider in game.

Hi, thanks a lot for your response, this helped me already ahead but my SphereCollider does not seem to work, and when I use these console commands nothing appears. I do take the quarry as the gameobject and try to add the component in there, is that wrong I wonder?
Check the code I added, this is what I was able to figure out so far, maybe you could guide me in to the right direction please?

private void OnServerInitialized()
        {
            _instance = this;

            var quarries = UnityEngine.Object.FindObjectsOfType<MiningQuarry>()?.ToList();
            if (quarries == null || quarries.Count == 0) return;

            foreach (var quarry in quarries)
            {
                var component = quarry.gameObject.AddComponent<QuarryTrigger>();
                component.transform.localPosition = quarry.ServerPosition;
                component.transform.localRotation = quarry.ServerRotation;
                component.SetRadius(0.5f);

                quarry.UpdateNetworkGroup();
                quarry.SendNetworkUpdateImmediate();
            }
        }

        private class QuarryTrigger : MonoBehaviour
        {
            public void SetRadius(float radius)
            {
                var collider = gameObject.AddComponent<SphereCollider>();
                collider.radius = radius;
                collider.isTrigger = true;
            }

            void OnTriggerEnter(Collider col)
            {
                BasePlayer player = col.gameObject.GetComponent<BasePlayer>();

                if (player == null || !player.IsValid() || player.IsNpc)
                    return;

                _instance.Puts($"Triggered by {player.displayName}");
            }
        }

Always create a new game object.

You can optionally parent it to the quarry if you want. That's a good idea because there are some plugins which can dynamically spawn and despawn quarries, so you get the destruction of your game object for free if you parent it.

For how to use ddraw, search the plugin list for keyword "ddraw" to find plugins that use it. You have to explicitly pass position, color, radius, duration, etc. Make sure you are an admin or your client will not draw anything.

Set the layer of the game object you create to the Trigger layer ((int)Rust.Layer.Trigger). This is important because it will dictate which objects can interact with the collider that you attach. This is another reason why you should use a separate game object, since you don't want to affect collision of the quarries themselves.

Another suggestion, never use FindObjectsOfType, especially to find entities. Instead, loop BaseNetworkable.server.entities.

NKXTQs24ExGTuL8.jpg WhiteThunder

Always create a new game object.

You can optionally parent it to the quarry if you want. That's a good idea because there are some plugins which can dynamically spawn and despawn quarries, so you get the destruction of your game object for free if you parent it.

For how to use ddraw, search the plugin list for keyword "ddraw" to find plugins that use it. You have to explicitly pass position, color, radius, duration, etc. Make sure you are an admin or your client will not draw anything.

Set the layer of the game object you create to the Trigger layer ((int)Rust.Layer.Trigger). This is important because it will dictate which objects can interact with the collider that you attach. This is another reason why you should use a separate game object, since you don't want to affect collision of the quarries themselves.

Another suggestion, never use FindObjectsOfType, especially to find entities. Instead, loop BaseNetworkable.server.entities.

Noted about that FindObjectsOfType, changed it.
But when I create a new GameObject, there is no parenting option, am I overlooking something here?

var obj = new GameObject();
obj.layer = (int)Layer.Trigger;

obj.SetParent is not found in here, and I also cant cast the obj to a BaseEntity seems like
Sorry if my questions sound stupid, I am fairly new to rust plugin development, I do have a C# background and I am trying to help out a community with some custom stuff, I really appriciate you trying to help out.

Since GameObject is a part of Unity, you have to parent at the Unity level.

obj.transform.parent = quarry.gameObject​.transform;

There is also a convenient helper in Rust: TransformEx.CreateChild, which creates the game object and parents it.

// UnityEngine.TransformEx
public static GameObject CreateChild(this GameObject go)
{
	GameObject gameObject = new GameObject();
	gameObject.transform.parent = go.transform;
	Identity(gameObject);
	return gameObject;
}

It's an extension method, so you can simply do:

var obj = quarry.gameObject.CreateChild();
NKXTQs24ExGTuL8.jpg WhiteThunder

Since GameObject is a part of Unity, you have to parent at the Unity level.

obj.transform.parent = quarry.gameObject​.transform;

There is also a convenient helper in Rust: TransformEx.CreateChild, which creates the game object and parents it.

// UnityEngine.TransformEx
public static GameObject CreateChild(this GameObject go)
{
	GameObject gameObject = new GameObject();
	gameObject.transform.parent = go.transform;
	Identity(gameObject);
	return gameObject;
}

It's an extension method, so you can simply do:

var obj = quarry.gameObject.CreateChild();

Somehow when I try to do this, and then I try to do for example: obj.gameObject.AddComponent<QuarryTrigger>(); with all the rest (setting radius, position of the trigger), it doesnt seem to fire at all when I enter it

I looked around a little bit about spheres and such and found some stuff, which I gave a try and somehow works as intented, I can even parent it and on Unload() I can safely remove them (we also do not have any other plugins that alter quarries), but with parent check it does work to remove only those made around static quarries.
This is the entire thing that works, the only issue I am having here is that I dont want the sphere to be visible (it has a slight blackisch transparent layer in game), just not sure how to remove this.

private void OnServerInitialized()
        {
            var quarries = BaseNetworkable.serverEntities.OfType<MiningQuarry>()?.ToList();
            if (quarries == null || quarries.Count == 0) return;

            foreach (var quarry in quarries)
            {
                if (quarry == null || !quarry.isStatic)
                    continue;

                BaseEntity sphere = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", quarry.ServerPosition, new Quaternion(), true);
                SphereEntity sphereEntity = sphere.GetComponent<SphereEntity>();
                sphereEntity.transform.localPosition = quarry.ServerPosition;
                sphereEntity.transform.localRotation = quarry.ServerRotation;
                sphereEntity.currentRadius = 34f;
                sphereEntity.lerpSpeed = 0f;
                
                var component = sphereEntity.gameObject.AddComponent<QuarryTrigger>();
                component.transform.localPosition = quarry.ServerPosition;
                component.transform.localRotation = quarry.ServerRotation;
                component.SetRadius(0.45f);

                sphere.SetParent(quarry, true, true);
                sphere.Spawn();

                quarry.UpdateNetworkGroup();
                quarry.SendNetworkUpdateImmediate();
            }
        }

        private void Unload()
        {
            var spheres = BaseNetworkable.serverEntities.OfType<SphereEntity>()?.ToList();
            if (spheres == null || spheres.Count == 0) return;

            foreach (var sphere in spheres)
            {
                if (sphere == null || !sphere.HasParent())
                    continue;

                var quarry = sphere.GetParentEntity() as MiningQuarry;
                if (quarry == null || !quarry.isStatic)
                    continue;

                sphere.SetParent(null, false, true);
                sphere.Kill();
            }
        }

        private class QuarryTrigger : MonoBehaviour
        {
            public void SetRadius(float radius)
            {
                var collider = gameObject.AddComponent<SphereCollider>();
                collider.radius = radius;
                collider.enabled = true;
                collider.isTrigger = true;
            }

            private void OnTriggerEnter(Collider col)
            {
                BasePlayer player = col.gameObject.GetComponent<BasePlayer>();
                if (player == null || !player.IsValid() || player.IsNpc)
                    return;

                player.ShowToast(GameTip.Styles.Blue_Normal, "IT WORKS!!!!", null);
            }
        }

But I guess this is the wrong way? Thought it works aside from the visibility on the sphere I do not want

The basic procedure is as follows.

  1. Create a game object
  2. Parent it, and/or change its position
  3. Set the layer of the game object
  4. Add a collider
  5. Set the collider as a trigger
  6. Size the collider
  7. Attach a custom component that watches for collision events
private class CollisionListener : MonoBehaviour
{
    private void OnTriggerEnter(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have entered the trigger");
    }

    private void OnTriggerExit(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have exited the trigger");
    }
}

private CollisionListener CreateTrigger(BaseEntity parent)
{
    var gameObject = parent.gameObject.CreateChild();
    gameObject.layer = (int)Rust.Layer.Trigger;

    var collider = gameObject.AddComponent<SphereCollider>();
    collider.isTrigger = true;
    collider.radius = 10;

    return gameObject.AddComponent<CollisionListener>();
}
NKXTQs24ExGTuL8.jpg WhiteThunder
private class CollisionListener : MonoBehaviour
{
    private void OnTriggerEnter(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have entered the trigger");
    }

    private void OnTriggerExit(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have exited the trigger");
    }
}

private CollisionListener CreateTrigger(BaseEntity parent)
{
    var gameObject = parent.gameObject.CreateChild();
    gameObject.layer = (int)Rust.Layer.Trigger;

    var collider = gameObject.AddComponent<SphereCollider>();
    collider.isTrigger = true;
    collider.radius = 10;

    return gameObject.AddComponent<CollisionListener>();
}

The basic procedure is as follows.

  1. Create a game object
  2. Parent it, and/or change its position
  3. Set the layer of the game object
  4. Add a collider
  5. Set the collider as a trigger
  6. Size the collider
  7. Attach a custom component that watches for collision events

Oh wow! I was so off then I assume, my way seems wrong then I assume as well.
I am going to try this out see how it goes but I do wonder how would I remove this child gameobject entity with its component (SphereCollider) from the quarry, in case of plugin unload, because I do see that AddComponent exists, but not RemoveComponent, and doing RemoveChild on the quarry itself is asking for a BaseEntity which the SphereCollider isnt. And I am not quite sure how I would just filter on the quarry on GameObject, that seems weird. But I would like to know how to reverse this, but all I am getting is problems when trying things out :P I def. need to find and look for some deeper documentation about all this



Merged post

Thought about something like this but that seems to fail out

private void Unload()
        {
            var quarries = BaseNetworkable.serverEntities.OfType<MiningQuarry>()?.ToList();
            if (quarries == null || quarries.Count == 0) return;

            foreach (var quarry in quarries)
            {
                if (quarry == null || !quarry.isStatic)
                    continue;

                var children = quarry.GetComponentsInChildren<SphereCollider>(true);
                if (children == null)
                    continue;

                foreach (var child in children.Where(x => x.isTrigger))
                {
                    // This fails
                    quarry.RemoveChild(child);
                }
            }
        }


Merged post

I also do not understand how my radius had to be 0.5f on the SphereCollider while your code use 10, yet ingame my radius was bigger as yours is lol I am confused! :D But yeah your code does work as intended in game, thank you a lot for all your time, effort and help.
Now I just gotta figure out how to delete them when I unload the plugin so we keep things clean.



Merged post

Ah, could it be possible and allowed in this stuff to create an internal List<CollisionListener>, add them all in there, then when doing Unload(), I basicly loop thru them and find the SphereCollider in them which I can ToBaseEntity() and then just Kill()..? or is it KillMessage() instead? So many questions!

If the radius is a different size for you, that's probably because of the SphereEntity you are using, which automatically scales its transform, thereby scaling children transforms in the process.

Here is a more complete example that takes care of cleanup in Unload. This approach maintains a static list. An alternate approach is to find all mining quarries and look for the object you created. That is easier when there is a component directly attached to the MiningQuarry since you can just call GetComponent<CollisionListener>(), but in this case, you would have to call GetComponentInChildren<CollisionListener>().

private class CollisionListener : MonoBehaviour
{
    private static List<CollisionListener> _allCollisionListeners = new List<CollisionListener>();

    public static void AddToEntity(BaseEntity entity)
    {
        var gameObject = entity.gameObject.CreateChild();
        gameObject.layer = (int)Rust.Layer.Trigger;

        var collider = gameObject.AddComponent<SphereCollider>();
        collider.isTrigger = true;
        collider.radius = 10;

        _allCollisionListeners.Add(gameObject.AddComponent<CollisionListener>());
    }

    public static void RemoveAll()
    {
        for (var i = _allCollisionListeners.Count - 1; i >= 0; i--)
        {
            DestroyImmediate(_allCollisionListeners[i].gameObject);
        }
    }

    private void OnTriggerEnter(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have entered the trigger");
    }

    private void OnTriggerExit(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have exited the trigger");
    }

    private void OnDestroy()
    {
        _allCollisionListeners.Remove(this);
    }
}

private void OnServerInitialized()
{
    foreach (var entity in BaseNetworkable.serverEntities)
    {
        var quarry = entity as MiningQuarry;
        if ((object)quarry == null)
            continue;

        CollisionListener.AddToEntity(quarry);
    }
}

private void Unload()
{
    CollisionListener.RemoveAll();
}
NKXTQs24ExGTuL8.jpg WhiteThunder

If the radius is a different size for you, that's probably because of the SphereEntity you are using, which automatically scales its transform, thereby scaling children transforms in the process.

Here is a more complete example.

private class QuarryCompanion : MonoBehaviour
{
    public static void AddToEntity(BaseEntity entity)
    {
        var companion = entity.gameObject.AddComponent<QuarryCompanion>();

        var gameObject = entity.gameObject.CreateChild();
        gameObject.layer = (int)Rust.Layer.Trigger;
        companion._child = gameObject;

        var collider = gameObject.AddComponent<SphereCollider>();
        collider.isTrigger = true;
        collider.radius = 10;

        gameObject.AddComponent<CollisionListener>();
    }

    public static void RemoveFromEntity(BaseEntity entity)
    {
        DestroyImmediate(entity.gameObject.GetComponent<QuarryCompanion>());
    }

    private GameObject _child;

    private void OnDestroy()
    {
        DestroyImmediate(_child);
    }
}

private class CollisionListener : MonoBehaviour
{
    private void OnTriggerEnter(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have entered the trigger");
    }

    private void OnTriggerExit(Collider collider)
    {
        var player = collider.ToBaseEntity().ToPlayer();
        if (player == null)
            return;

        player.ChatMessage("You have exited the trigger");
    }
}

private void OnServerInitialized()
{
    foreach (var entity in BaseNetworkable.serverEntities)
    {
        var quarry = entity as MiningQuarry;
        if ((object)quarry == null)
            continue;

        QuarryCompanion.AddToEntity(quarry);
    }
}

private void Unload()
{
    foreach (var entity in BaseNetworkable.serverEntities)
    {
        var quarry = entity as MiningQuarry;
        if ((object)quarry == null)
            continue;

        QuarryCompanion.RemoveFromEntity(quarry);
    }
}

Holy moly I love you man!! This morning I had no idea I would actually have somebody who could bring me some examples and help/information to learn more about this, this really helped a LOT, I was def. not on the right path and I thank you for the time and effort you provide to put me on the right path. All this for a trigger it is def. something to look into a little to understand this better. But faily said I am new to all this stuff, specially with Rust plugins and UnityEngine itself, but I am having a lot of fun trying all this. 
I need to look into this code and try to understand everything that is going on so it becomes easier in future projects.

I had no idea about the scaling thing with my first code, will try to remember that.
I also didnt knew about DestroyImmediate()
There is a few things I do not understand instantly but I am trying to follow the code step by step to get a hang of it

Is it also possible to combine the QuarryCompanion and the CollisionListener so the OnTriggerEnter (i dont need the OnTriggerExit) can be put all into QuarryCompanion or is it really required to be seperated?



Merged post

Oh I didnt see you altered the message, forget my question at the end of the previous reply :P

Merged post

Haha now I have 2 examples, but which on to go and use, thought the second one seems less code/confusion to me, but is it better as the first to use..? :P I cant thank you enough for your help man this is really helping me out a lot, everyday more and more I start to understand about this.

Merged post

I see what you mean now as well with the GetComponentInChildren<CollisionListener>(), trying some stuff out to see what results I am getting.
From what I am getting, I seem to understand what you mean with the alternative approach, which could be indeed something to use instead, making it so the list is no longer required, saves memory too after all. Def. testing things out at the moment this code you gave me helps so much! :D

I like both approaches you gave me, and I will def. play around a little to see what fits the best but I do think the second code you gave me is a bit easier to grab on and understand what is going on. But that can also just be me :P Thanks a lot again for your help.



Merged post

I used the second code you gave me, with some changes, if wanted I can post the 'final' result in here once.
Now I can continue for the next things, this was only one step but holy moly if I knew triggers had to be made this way :P but its def. still a learning progress. I hope the community I am doing this for will enjoy my work, and if not then it is what it is, at least I had fun doing this and learning this.
But hell yeah this is working def. as intended! :D 4 in the morning, time for some sleep.

Thanks again for the help.

Locked automatically