1. This forum is obsolete and read-only. Feel free to contact us at support.keenswh.com

Modders can now create their own terminal interfaces - How?

Discussion in 'Modding API' started by Leto, May 22, 2016.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. Leto

    Leto Trainee Engineer

    Messages:
    12
    "Modders can now create their own terminal interfaces"
    Does anyone have more information about this?
     
    • Agree Agree x 2
  2. Knsgf

    Knsgf Junior Engineer

    Messages:
    538
    By calling methods in MyAPIGateway.TerminalControls class. The widgets themselves are defined in Sandbox.ModAPI.Interfaces.Terminal namespace.

    Here is an example that adds "Cruise Control" toggleable switch to a cockpit:
    Code:
            bool CruiseControlSwitchGetter(IMyTerminalBlock cockpit)
            {
                // This method is called when user opens Control Panel to determine the current state of the control for a given TerminalBlock (on or off in our example)
    
                return IsGridCuriseControlCurrentlyActive(cockpit.CubeGrid);    // Defined somewhere else in mod code
            }
    
            void CruiseControlSwitchSetter(IMyTerminalBlock cockpit, bool newState)
            {
                // This method is called when user clicks on the switch in the Control Panel. newState is true if switch is turned on and false otherwise
    
                SetGridCuriseControl(cockpit.CubeGrid, newState);   // Defined somewhere else in mod code
            }
    
            void CruiseControlSwitchToggle(IMyTerminalBlock cockpit)
            {
                // This method is called when user, sensor, button panel, timer or programmable block toggles the switch via hotbar action
    
                SetGridCuriseControl(cockpit.CubeGrid, !IsGridCuriseControlCurrentlyActive(cockpit.CubeGrid));
            }
    
            void CruiseControlSwitchHotbarText(IMyTerminalBlock cockpit, StringBuilder hotbarText)
            {
                // This method displays switch status on the hotbar (e. g. Open and Closed for doors)
    
                hotbarText.Clear();
                hotbarText.Append(IsGridCuriseControlCurrentlyActive(cockpit.CubeGrid) ? "Cruise" : "Stop");
            }
    
            void CreateCruiseControlSwitch()
            {
                // This method is called once during mod initialisation
    
                var cruiseControlSwitch = MyAPIGateway.TerminalControls.CreateControl<IMyTerminalControlOnOffSwitch, Sandbox.ModAPI.Ingame.IMyCockpit>("CruiseControlOnOff");
                cruiseControlSwitch.Getter  = CruiseControlSwitchGetter;
                cruiseControlSwitch.Setter  = CruiseControlSwitchSetter;
                cruiseControlSwitch.Title   = MyStringId.GetOrCompute("Cruise control enable");
                cruiseControlSwitch.OnText  = MyStringId.GetOrCompute("Cruise");    // Typically "On" but can be any text
                cruiseControlSwitch.OffText = MyStringId.GetOrCompute("Stop");      // Typically "Off" but can be any text
                MyAPIGateway.TerminalControls.AddControl<Sandbox.ModAPI.Ingame.IMyCockpit>(cruiseControlSwitch);
    
                // The switch is now functional, however user can only access it by going to Control Panel menu itself.
                // To allow toggling this switch via hotbar, sensor, button panel, timer or programmable block, a terminal action is needed as well.
                var cruiseControlSwitchAction = MyAPIGateway.TerminalControls.CreateAction<Sandbox.ModAPI.Ingame.IMyCockpit>("CruiseControlOnOffToggle");
                cruiseControlSwitchAction.Action = CruiseControlSwitchToggle;
                cruiseControlSwitchAction.Name   = new StringBuilder("Cruise control On/Off");
                cruiseControlSwitchAction.Writer = CruiseControlSwitchHotbarText;
                MyAPIGateway.TerminalControls.AddAction<Sandbox.ModAPI.Ingame.IMyCockpit>(cruiseControlSwitchAction);
            }
    
    One caveat though: the state of custom controls is not saved by the game; your mod must implement its own save mechanism.
     
  3. Leto

    Leto Trainee Engineer

    Messages:
    12
    Thank you very much.
    Then my next question: Is there a tutorial how to save stuff?
     
  4. Digi

    Digi Senior Engineer

    Messages:
    2,393
    Save settings for block's custom stuff ? There's no easy way to do that, known choices so far are:

    - save in world settings using MyAPIGateway.Utilities.Set/GetVariable, which won't support blueprints, but the data is deleted with the world
    - save in mod storage folder (still in MyAPIGateway.Utilities ) which also isn't going to support blueprints, but is also not deleted when the world is deleted
    - save in block's name using some kind of serialization that will easily isolate your settings and allow other mods to also add their settings without collision, this of course will be compatible with blueprints and will be deleted with world.

    None of the options' data will be deleted when the mod is removed, but the names one you can manually remove since they're easily editable.

    In my opinion the best choice for functionality is the name if you really want people to be able save their stuff in blueprints. Otherwise just save to world. Saving this stuff to mod storage is not that ideal.

    -------
    EDIT:

    By the way, it would be best to add your terminal UI in the first update of the block (once per world though) as there's an issue currently with MP clients that will lose vanilla UI elements specific to the block (it happened to me with PB but it's most likely not limited to that).

    ----

    EDIT in 2017:

    @Pharap since you tagged this informative I guess you read it but didn't mind the date... this is outdated stuff!

    Now you have the per-entity mod storage thing which is not synchronized and you also have the player-visible CustomData field which is synchronized automatically.

    More info on the mod storage: https://forum.keenswh.com/threads/modapi-changes-12-8.7389842/
     
    Last edited: Feb 27, 2017
    • Informative Informative x 2
  5. Leto

    Leto Trainee Engineer

    Messages:
    12
    Thank you Digi. This was very helpful
     
  6. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    I have a related question. The example above seems to add cruise control to ALL cockpit blocks in the game.

    MyAPIGateway.TerminalControls.AddAction<Sandbox.ModAPI.Ingame.IMyCockpit>(cruiseControlSwitchAction);

    This line seems to add to all IMyCockpit blocks regardless of subtype. So if you make a new cockpit with a different subtype and try to slap on some new controls, the base cockpit will also have them. How do you make sure controls are only added to 1 subtype? I have spent a lot of time trying to figure this out, but I am stuck here.

    EDIT: Just to be clear, I did include the subtype in the EntityComponentDescriptor, but adding the controls seems to ignore the subtype.

    EDIT2: I think I have it figured out. You have to get the list of controls for a block and add a list of controls, while requiring that the subtype of this block is correct. You use
    MyAPIGateway.TerminalControls.CustomControlGetter
     
    Last edited: May 25, 2016
  7. Phoera

    Phoera Senior Engineer

    Messages:
    1,713
    Code:
    /// <summary>
            /// This event allows you to modify the list of actions available when a user wants to select an action for a block in the toolbar.  Modifying the list
            /// in this event modifies the list displayed to the user so that you can customize it in specific situations (like blocks with different subtypes,
            /// or even on specific blocks by entityId)
            /// </summary>
            event CustomActionGetDelegate CustomActionGetter;
    
            /// <summary>
            /// This event allows you to modify the list of controls that the game displays when a user selects a block.  Each time terminal controls are
            /// enumerated for a block, this delegate is called, which allows you to modify the control list directly, and remove/add as you see fit before
            /// the controls are dispalyed.  This is to allow fine grain control of the controls being displayed, so you can display only controls you want to
            /// in specific situations (like blocks with different subtypes, or even on specific blocks by entityId)
            /// </summary>
            event CustomControlGetDelegate CustomControlGetter;
    
    /// <summary>
        /// Allows you to modify the actions associated with a block before it's displayed to user.
        /// </summary>
        /// <param name="block">The block actions are associated with</param>
        /// <param name="actions">The list of actions for this block</param>
        public delegate void CustomActionGetDelegate(IMyTerminalBlock block, List<IMyTerminalAction> actions);
    
    /// <summary>
        /// Allows you to modify the terminal control list before it is displayed to the user.  Modifying controls will change which controls are displayed.
        /// </summary>
        /// <param name="block">The block that was selected</param>
        /// <param name="controls"></param>
        public delegate void CustomControlGetDelegate(IMyTerminalBlock block, List<IMyTerminalControl> controls);
    
    i think summary is rather speaking.
     
    • Informative Informative x 1
  8. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    phoenixcorp: Thanks, yea I just figured that out myself after being stuck for hours. Good idea to post it here. Others are going to get stuck as well.
     
  9. Digi

    Digi Senior Engineer

    Messages:
    2,393
    You can also use the Visible for all your custom controls and check there if the block is your subtype one.

    Currently the PB can only get values that are added normally but I guess they'll fix that in the future.
     
  10. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    Are you saying CreateProperty is bugged? What else would a property be useful for if not to access through the PB?

    Code:
            /// <summary>
            /// This creates a property that can be added to a block.  A property is not visible on the terminal screen but can hold a value that can be used in
            /// programmable blocks.
            /// </summary>
            /// <typeparam name="TValue">The type of property you're creating</typeparam>
            /// <typeparam name="TBlock">The ModAPI interface of the associated block</typeparam>
            /// <param name="id">A unique identifier for this property</param>
            /// <returns>Returns an IMyTerminalControlProperty that can be added to a block via AddControl</returns>
            IMyTerminalControlProperty<TValue> CreateProperty<TValue, TBlock>(string id);

    Edit: Oh and if it is bugged, is it possible to mod the PB to use the properties? I'm guessing not, but worth asking.
    --- Automerge ---
    By the way: I think I ran into a different bug:

    If I use MyAPIGateway.TerminalControls.CustomActionGetter when the block has 0 actions and I add an actionlist to it, nothing will happen. If I first add a single action with MyAPIGateway.TerminalControls.AddAction<IMyTerminalBlock> then I can add a list of actions without problems, even if I use .Clear first. It seems like blocks that normally have no actions can't be given actions with the CustomActionGetter without creating a new list through AddAction first. It's kind of annoying because AddAction adds the Action to ALL blocks that use IMyTerminalBlock. So what I would have to do is Add actions to all these blocks, then remove them all and then add actions to my specific block, just to make sure it has an actionlist to be added to.

    You will probably not run into this if you mod a block that already has actions. I did this with a block set to objectbuilder: "IMyTerminalBlock".
     
  11. Digi

    Digi Senior Engineer

    Messages:
    2,393
    No, adding them with AddControl() will work fine for PBs (which you need to use to add properties created with CreateProperty() as well).

    What I'm saying is adding things via CustomActionGetter or CustomControlGetter are not yet readable by PB from what I know. Just test whatever you're trying to do to make sure it works in all things :p

    And any bugs you find you should take to the one who added the feature, which is Tyrsis.
     
  12. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    Ahh, thanks for that clarification. I am not worried about that then. That's just a matter of copying all your controls into a property-version and let that be accessed by PB. Basically you have a variable in a mod that can be changed by a terminalcontrol, but is also copied from a terminalproperty that is changed in a PB script.
     
  13. Knsgf

    Knsgf Junior Engineer

    Messages:
    538
    When you call CreateControl(), a PB property with a name identical to control ID is automatically created for you. There is no need to separately call CreateProperty(), unless you want a property that isn't directly associated with any control.
     
  14. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    Awesomeness!
     
  15. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    I made a bunch of controls, actions and properties in my mod script, but I don't know how to access them from the PB. Is there some trick to it? Is there any mod released yet that uses custom properties that I can have a peek at? I have a block of RadioAntenna type with a different subtype (LargeHoloProjector) in the cubeblocks.sbc. Do I cast the block as IMyRadioAntenna in my PB and then do ".property" on it? That didn't work for me. IMyTerminalBlock did not work either.

    here's a part of the code from my mod script:

    Code:
    .....
    using Sandbox.Game.EntityComponents;
    
    namespace Holomap
    {
    .......
    [MyEntityComponentDescriptor(typeof(MyObjectBuilder_RadioAntenna), new string[] { "LargeBlockHoloProjector", "SmallBlockHoloProjector" })]
        public class HoloProjector : MyGameLogicComponent
        {
        .....
        private Vector3D SelectedCoordsGetter(IMyTerminalBlock projector)
            {
                return Coords;
            }
          .......
        
    private void AddProgblockProperties()
            {
      
                IMyTerminalControlProperty<Vector3D> selectedCoords = MyAPIGateway.TerminalControls.CreateProperty<Vector3D, Sandbox.ModAPI.Ingame.IMyRadioAntenna>("SelectedCoords");
    
                selectedCoords.Getter = SelectedCoordsGetter;
    
                projectorControls.Add(selectedCoords);
            }
    ......
    AddProgblockProperties();
    }
    The full script is over 1000 lines long and all the controls and actions work. I just don't know how to acces the properties from the PB. I am probably forgetting something silly....
     
  16. Digi

    Digi Senior Engineer

    Messages:
    2,393
    ".property" ? It should be GetValue().
    Code:
    block.GetValue<Vector3D>("SelectedCoords");
    Still, I believe won't work if you add properties in the CustomControlGetter.
    You can add it normally with AddControl() and use Visible to disable it if the block is not your subtypeid.
     
  17. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    You were correct. It does not work with the CustomControlGetter and it does work with AddControl. I can confirm that.
    Thanks again!
    --- Automerge ---
    Ok, so:

    CustomControlGetter may add to single specific block with (for example):

    Code:
     MyAPIGateway.TerminalControls.CustomControlGetter += (x, controlList) =>
                    {
                        if (x.EntityId != id) return;
                        for (int i = controlList.Count - 1; i > 4; i--)
                        {
                            controlList.RemoveAt(i);
                        }
                        controlList.AddList(projectorControls);
                    };
    Unfortunately it does not add properties for the PB :(

    AddControl adds to all blocks of a certain type for example with:

    Code:
    MyAPIGateway.TerminalControls.AddControl<Sandbox.ModAPI.Ingame.IMyRadioAntenna>(properties[i]);
    However, if I do this just for the properties, then all my regular antennas will have them too and these properties will give the values of whatever the first modded block is producing. I tried something with .Visible as you said, but I am not certain about what it does and whether I am doing it right (probably not). Remember I am running a script that attaches to a specific entity with MyEntityComponentDescriptor and MyGameLogicComponent, which means every new block of the same subtype is going to run the script and possibly add duplicate properties to all of them on the first update. Or maybe they will overwrite, I don't know.

    This is what I tried with .Visible:

    Code:
    for (int i = 0; i < properties.Count; i++)
                    {
                        properties[i].Visible = (x) =>
                        {
                            if (x.EntityId != id) return false;
                            else return true;
                        };
                        MyAPIGateway.TerminalControls.AddControl<Sandbox.ModAPI.Ingame.IMyRadioAntenna>(properties[i]);
                    }
    Of course thats just gonna return true whenever the Entity runs its script. How would I go about setting the properties invisible for all blocks except for my specific entity ID?
     
  18. Digi

    Digi Senior Engineer

    Messages:
    2,393
    subtypeId, simply make a hashset of your Ids and check if it contains in that Visible event.
    Code:
    property.Visible = (b) => yourIdsHashSet.Contains(b.BlockDefinition.SubtypeId);
    However, I'm not sure if you'll be still able to use that property for normal antennas within PB... if it does please do tell :p
     
  19. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    If I filter on SubtypeId, will I not still run into the problem that two of my modded blocks will interfere with eachothers properties? If I add the properties of block 1, I will also add it to block 2, which means a programmable block using the properties of block 2, will get property values from block 1, or vice versa. This is what is happening right now when I use two modded blocks with the same subtype and that is why I am trying to filter for EntityID instead of subtypeId. I did that for the controls and that was the only way to add controls to a specific block. It might also be that I am not using my Getter right.

    Possibly I am misunderstanding your purpose with that piece of code, I am lacking in some areas when it comes to coding. I'll do some more digging tomorrow.
     
  20. Digi

    Digi Senior Engineer

    Messages:
    2,393
    That's what the events are for, the block in the event is the block that is currently requesting the stuff.
    If you get/set the values properly there's no collision.
    Code:
    property.Getter = (b) => b.GameLogic.GetAs<YourBlockLogicClass>().GetTheValueThing;
    If it wasn't clear, YourBlockLogicClass is the HoloProjector from your previous codes.

    Also, you could probably get faster support on the discord chat and from more people :p you can find that on the forum's front page on the right side.
     
    • Informative Informative x 1
  21. Gwindalmir

    Gwindalmir Senior Engineer

    Messages:
    1,006
    For the best working example, please see my Beam Drill mod: https://steamcommunity.com/sharedfiles/filedetails/?id=681276386
    I have two blocks that I add controls to: a drill, and a gatling turret.

    It's a complete implementation which adds only to specific blocks (doesn't add custom controls to stock or other modded blocks), it also removes stock controls that are not appropriate for my mod's purposes. It also handles saving the data and syncing to clients, as well as adding appropriate toolbar actions (and removing inappropriate ones).

    It shows how to save the data to the world, using the first method Digi described, as well as syncing the data to all the clients. Your own syncing is required, as the game will not sync terminal control states itself.

    The mod is large, but it shows you *everything*.

    Start with the method CreateTerminalControls in both LaserDrill.cs and LaserDrillTurret.cs.
     
    • Informative Informative x 3
  22. Innoble

    Innoble Apprentice Engineer

    Messages:
    238
    Digi and Phoenix: Very cool you're taking the time to help me like like this. I bet other people who find this thread save a lot of time. I will take a look at Phoenix' mod and I think I can solve my problems with Digi's advice.

    Edit: Phoenix, you were not kidding. It is very large. It will take me some time to get through it, but almost everything that my mod is still missing you are using in some way. Looking at your mod is probably all the help I will need trying to understand the C# syntax. :tu:

    EDIT2: I have a first release version for my mod. Thanks again for the assistance.
     
    Last edited: May 30, 2016
  23. Takeshi

    Takeshi Apprentice Engineer

    Messages:
    200
    is there a way to create a drop down field and i can fill it before it displays?
    (for example with grid or blocknames)
     
  24. Digi

    Digi Senior Engineer

    Messages:
    2,393
    The drop down has a ComboBoxContent field which gets called to fill the stuff, so that's where you fill things before it gets shown.

    It might get refreshed when you refresh the control but I never tested.
     
  25. Phoera

    Phoera Senior Engineer

    Messages:
    1,713
    btw, i am really must add property/control to all types of blocks?
    in my mod i tryed use IMyShipController, but it did not worked, so i was forced to do it separately for IMyCockpit and IMyRemoteControl.

    anyway to avoid this?(i don't wanna create huge amount of initers)
     
  26. Digi

    Digi Senior Engineer

    Messages:
    2,393
    Some interfaces work some don't, I dunno why but that's how it works, it requires experimentation.

    However you don't need huge amount of code for repeating something, copy paste isn't a good way to repeat stuff if it can be helped by code.


    It also depends on how 'real' you want the controls to be, if you don't care about them being accessible by PB you can simply hook the CustomControlGetter even and add them there where you can check if block is IMyShipController or even add to all IMyTerminalBlocks.



    But if you do want them to be readable by PB you need to add them properly, so try this:

    Create the controls for the IMyTerminalBlock (without adding them to it) somewhere isolated and then add them to each block type when they're first initialized.

    Have multiple tagged classes that extend a class which extends the game logic component.
    Example from my mods that hook both gravity generator types with a single code:

    Since you need to add them once to a type, you also need a static HashSet<MyObjectBuilderType> which you check/add in Init() where you add the previously created terminal controls.
     
  27. Phoera

    Phoera Senior Engineer

    Messages:
    1,713
    i already use this way,i hope to evade it.(actully class a bit mor complex, cuz one bug)

    ok will continue use it then.

    thank you
     
  28. theimmersion

    theimmersion Trainee Engineer

    Messages:
    11
    Hey, im new to modding and really need help. Could you please finish this script code so it works by simply copy pasting in the .cs file with the best solution for saving the current status of the check box?
    So when i load up the game, it rememberes that i am in cruise mode and keeps moving?
    Also, could you possibly add a slider to set the top speed for it?
    Something between min 0 and max 10000 m/s for those with no speed limit. :)
    Would the checkbox work in cockpit toolbar?
    That would be amazing to show how to do stuff like these.
    Id try than to put it on remote control block to see if i got things right.
    That would help a lot to figure out how to add stuff to all of the in game objects.
    I tired copy pasting this and started the game but it didnt work. SE is really low on guides.
    Please help us! And btw, thanks for the awesome control module mod.

    I can than make experiments with different options and functions. But i need a working example of exactly this script. Something that adds buttons or even sliders to all objects of a given type. Like pistons thrusters etc. Also, i did setup my IDE to use the .dll and it auto fills stuff but where do i put the source code? do i copy it in the bin folder where the .dll is located? How would the structure look? For instance:
    C:\games\steam\steamapps\space engineers\bin64\SpaceEngineers-master
    or perhaps the contents of the SpaceEngineers-master folder into the root bin64?
    I really want to add this cruise control to all cockpits. Also, will it affect the cockpits that are added via mods?

    Among many stuff i tried, this is the last for tonight. Its 2 am and i just cant figure it out.

     
    Last edited: Dec 7, 2016
Thread Status:
This last post in this thread was made more than 31 days old.