Page 2 of 2

Re: Writing to Status or top of screen text?

Posted: Mon Dec 02, 2019 9:24 pm
by l3lessed
If you want to create a popup ui, I can show you how. I had to figure the UI system out to get some values into it. However, as stated, this is done through card coded c# scripts and direct engine coding, as there is not way to call these message box components through the modding system. But, as long as you import the correct engine components into your script for calling the message box and text tokens, you should be able to create your own message boxes this way. Below are the two components to call from to setup the UI message box.

Engine Components to Use:
DaggerfallConnect.Arena2
DaggerfallWorkshop.Game.UserInterface

First you need to create a text file token to store the text that will be displayed, like so. If you're going to have more than one text line within the message box, ensure to size the text tokens array for each line of text, including text formatting/line breaks, so there are enough tokens in the array for every text line, including line breaks.

Code: Select all

TextFile.Token[] tokens = "Text to display";
Then you use the messagebox class to call and setup a new ui messagebox through the uiManager; Just decide if it will be part of a already existing UI reference, like the inventory window, and put it into the second spot, where "PreviousUIReference" is, like so. if you don't have a previous UI to close out to, then it can be ignored. It defaults to a Null when there is no input sending player back to fps view.

Code: Select all

DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, PreviousUIReference);
Next, load up the messagebox item with the display tokens you created, like so. If you are pulling any text data from default DFEngine items, you can use the MacroContextProvider to retrieve that hard coded data(Item Weight, Value, ect.). Again, if you don't need it, ignore that as the routine defaults it to null and ignores it.

Code: Select all

messageBox.SetTextTokens(tokens, MacroContextProvider);
Then finally, tell the engine to enable close on clicking anywhere, so the user can close out of the messagebox, and then call the messagebox to show, like so.

Code: Select all

                    
                    messageBox.ClickAnywhereToClose = true;
                    messageBox.Show();
                    
At this point, the engine should read the text tokens, put them into the messagebox UI item, render it, and then close it out on clicking anywhere on screen.

Here is my example for creating range and inertia values in the UI readout.

Code: Select all

 protected void ShowInfoPopup(DaggerfallUnityItem item)
        {
            TextFile.Token[] tokens = ItemHelper.GetItemInfo(item, DaggerfallUnity.TextProvider);

            //checks if an item has a usable range. If it does, resizes above text token array to add 2 blank tokens to it.
            //Creates range string for itemdisplay ui, adds a formatting token to first blank token then adds text to second
            //blank token.
            if (item.rangeInFt != 0)
            {
                Array.Resize(ref tokens, tokens.Length + 4);
                int addToken = tokens.Length;
                string rangetxt = string.Format("Range: {0} feet", item.rangeInFt);
                string speedtxt = string.Format("Intertia: {0} lb/ft", ItemHelper.getItemSpeed(item));
                tokens[addToken - 1] = TextFile.CreateFormatToken(TextFile.Formatting.JustifyCenter);
                tokens[addToken - 2] = TextFile.CreateTextToken(speedtxt);
                tokens[addToken - 3] = TextFile.CreateFormatToken(TextFile.Formatting.JustifyCenter);
                tokens[addToken - 4] = TextFile.CreateTextToken(rangetxt);
            }

            if (tokens != null && tokens.Length > 0)
            {
                DaggerfallMessageBox messageBox = new DaggerfallMessageBox(uiManager, this);
                messageBox.SetTextTokens(tokens, item);

                if (item.IsPotionRecipe)
                {   // Setup the next message box with the potion recipe ingredients list.
                    DaggerfallMessageBox messageBoxRecipe = new DaggerfallMessageBox(uiManager, messageBox);
                    messageBoxRecipe.SetTextTokens(item.GetMacroDataSource().PotionRecipeIngredients(TextFile.Formatting.JustifyCenter));
                    messageBoxRecipe.ClickAnywhereToClose = true;
                    messageBox.AddNextMessageBox(messageBoxRecipe);
                    messageBox.Show();
                }
                else if (item.legacyMagic != null)
                {   // Setup the next message box with the magic effect info.
                    DaggerfallMessageBox messageBoxMagic = new DaggerfallMessageBox(uiManager, messageBox);
                    messageBoxMagic.SetTextTokens(1016, item);
                    messageBoxMagic.ClickAnywhereToClose = true;
                    messageBox.AddNextMessageBox(messageBoxMagic);
                    messageBox.Show();
                }
                else if (item.ItemGroup == ItemGroups.Paintings)
                {   // Setup the message box with the painting image generated by macro handlers
                    ImageData paintingImg = ImageReader.GetImageData(item.GetPaintingFilename(), item.GetPaintingFileIdx(), 0, true, true);
                    messageBox.ImagePanel.VerticalAlignment = VerticalAlignment.None;
                    messageBox.ImagePanel.Position = new Vector2(0, 5);
                    messageBox.ImagePanel.Size = new Vector2(paintingImg.width, paintingImg.height);
                    messageBox.ImagePanel.BackgroundTexture = paintingImg.texture;
                    messageBox.ClickAnywhereToClose = true;
                    messageBox.Show();
                }
                else
                {
                    messageBox.ClickAnywhereToClose = true;
                    messageBox.Show();
                }
            }
        }
Also, about the message spam issue, you need to understand how script and cpu cycles work if you plan on modding more. When you initiate any script file, it's update routine will run every single cpu cycle (AKA, every rendered frame). So, if you just drop a message into a script and put in a message command into the update routine, it will pop up every single frame. This is important because, if you do not code considering cycle loads/frame renders, you will quickly create bloated mods that don't play well with other things or create memory leaks or have other unforeseen bugs. Think of how many cycles are being wasted by forcing the engine to compute, push through, and render a text message very single frame.

You should be using either a boolean switch to trigger on and off the message when certain events trigger or a delta timer to set the time intervals for the message to appear or a combination of a timer and trigger switch to turn on the message and then repeat it at a set time interval; this is how the poison message works.

By using proper boolean switches, routine calls, coroutines, and timers, you can improve the efficiency and playability of all your mods and code.

Just always ask yourself, does this code executing do anything for the script/program in the coding execution flow. If not, you can probably get rid of it or recode it to free up a few more cycles/frames for the engine to use on more important things. Optimized code should only fire when the data/process is needed, and not on any other cpu cycle to ensure it doesn't affect further scripts and coding running in subsequent cpu cycles. Bethesda Engines have a long history of having issue with running extra script loads outside the programmed engine; Skyrim can only handle the modding it can because of the intense amount of work and millions of combined modding hours the whole community has put into rebuilding the game and engine from ground up to deal with Bethesda's cruddy development and coding practices and cycles.

Re: Writing to Status or top of screen text?

Posted: Tue Dec 03, 2019 8:40 am
by Ralzar
Thank you for the primer. I am learning as I go here. I have a good gut feeling for how this stuff works, but no experience to draw on :D

At the moment I actually solved the problem by implementing a function where you look up at the sky to trigger a midscreen text informing you of the weather temperature. Which feels nice and interactive.

Once I've implemented the currently feasible features (which should be soon) I will start going over the code to consider if it could be made briefer. At the moment it uses EntityEffectBroker.OnNewMagicRound to apply effects, but I feel I am putting too much code in here that could probably be handled another way. Spamming is generally avoided by using counters and if/else statements.