• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Solved] Is there a way to tie the Upkeep messages to something other than food count?

Level 9
Joined
Mar 17, 2016
Messages
151
So basically I'm wondering if I can make the Upkeep Messages (No Upkeep, Low Upkeep, High Upkeep) pop up based on triggers or other in-game data instead of just the amount of food used.

In the map I'm making I think it would be a nice quality of life to have the message display (which has been replaced with custom text) at the top of the screen, rather than just using the -"Show "Message" to (All Players) function.


I can't find anything about this so im not sure if it's possible, but if it is I would love to know. Either way it's completely optional for the map, but still I thought I'd see.


Cheers
 
Level 24
Joined
Jun 26, 2020
Messages
1,914
If you are in older versions, the more you can do is change the message in the gameplay constants and messing with the food to show the messages, but you can only use 3 different text that are pre-determined (if you wonder how Dota All Stars does it, it uses Memory Hack).
If you are in newer versions, you can instead use custom frames to do it, here you can learn how: [JASS/AI] - The Big UI-Frame Tutorial
 

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,371
So basically I'm wondering if I can make the Upkeep Messages (No Upkeep, Low Upkeep, High Upkeep) pop up based on triggers or other in-game data instead of just the amount of food used.

In the map I'm making I think it would be a nice quality of life to have the message display (which has been replaced with custom text) at the top of the screen, rather than just using the -"Show "Message" to (All Players) function.


I can't find anything about this so im not sure if it's possible, but if it is I would love to know. Either way it's completely optional for the map, but still I thought I'd see.


Cheers
Do you rely on Food at all? If not, you could easily manipulate these values via triggers to show the errors.

Anyway, what Herly said about UI frames is what I recommend. But you need to be on version 1.31+ for it to work. I could even create an example for you, a simple text messages is quite easy to setup.
 
Level 9
Joined
Mar 17, 2016
Messages
151
Do you rely on Food at all? If not, you could easily manipulate these values via triggers to show the errors.

Anyway, what Herly said about UI frames is what I recommend. But you need to be on version 1.31+ for it to work. I could even create an example for you, a simple text messages is quite easy to setup.
I could potentially work around the food source, but if the UI thing isnt going to cause issues then it could be great.

If you're willing to make an example that i could use that would be awesome
 

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,371
I could potentially work around the food source, but if the UI thing isnt going to cause issues then it could be great.

If you're willing to make an example that i could use that would be awesome
Here you go, a GUI friendly way to create an "error/upkeep" style text message anywhere on the screen with custom settings like scaling the size of the text. Since it's meant to match the behavior of Warcraft 3's "error/upkeep" messages, only one message can be displayed at a time per player, with the most recently sent message taking priority.

[REQUIRED]
vJASS:
library CustomErrorMessage initializer Init

    globals
        private framehandle array Message_Frame
        private timer array Message_Timer
        private hashtable Message_Table = InitHashtable()
    endglobals

    // REQUIRES THESE GUI VARIABLES:
    // CEM__Text = String
    // CEM__Duration = Real
    // CEM__Scale = Real
    // CEM__Position_X = Real
    // CEM__Position_Y = Real
    // CEM__Max_Width = Real
    // CEM__Max_Height = Real
    // CEM__Player = Player

    private function UpdateMessageForPlayer takes integer id returns nothing
        call BlzFrameSetScale(Message_Frame[id], udg_CEM__Scale)
        call BlzFrameSetAbsPoint(Message_Frame[id], FRAMEPOINT_CENTER, udg_CEM__Position_X, udg_CEM__Position_Y - (-0.04 + (udg_CEM__Scale * 0.04)))
        call BlzFrameSetSize(Message_Frame[id], udg_CEM__Max_Width, udg_CEM__Max_Height)
        call BlzFrameSetText(Message_Frame[id], udg_CEM__Text)
    endfunction

    private function RemoveMessageOnExpire takes nothing returns nothing
        local integer id = LoadInteger(Message_Table, GetHandleId(GetExpiredTimer()), 0)
        call BlzFrameSetText(Message_Frame[id], "")
    endfunction

    private function SendMessageToAllPlayers takes nothing returns nothing
        local player p = GetEnumPlayer()
        local integer id = GetPlayerId(p)
        if Message_Frame[id] == null then
            set p = null
            return
        endif
        call UpdateMessageForPlayer(id)
        call TimerStart(Message_Timer[id], udg_CEM__Duration, false, function RemoveMessageOnExpire)
        call SaveInteger(Message_Table, GetHandleId(Message_Timer[id]), 0, GetPlayerId(p))
        set p = null
    endfunction

    private function SendMessageToOnePlayer takes nothing returns nothing
        local integer id = GetPlayerId(udg_CEM__Player)
        if Message_Frame[id] == null then
            return
        endif
        call UpdateMessageForPlayer(id)
        call TimerStart(Message_Timer[id], udg_CEM__Duration, false, function RemoveMessageOnExpire)
        call SaveInteger(Message_Table, GetHandleId(Message_Timer[id]), 0, id)
    endfunction

    private function CreateTextFrames takes nothing returns nothing
        local integer id = 0
        loop
            exitwhen id > 23
            set Message_Frame[id] = BlzCreateFrameByType("TEXT", "MyTextFrame", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
            call BlzFrameSetScale(Message_Frame[id], 1)
            call BlzFrameSetAbsPoint(Message_Frame[id], FRAMEPOINT_CENTER, 0.4, 0.3)
            call BlzFrameSetSize(Message_Frame[id], 0.3, 0.1)
            call BlzFrameSetTextAlignment(Message_Frame[id], TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_CENTER)
            call BlzFrameSetText(Message_Frame[id], "")
            call BlzFrameSetEnable(Message_Frame[id], false)
            call BlzFrameSetVisible(Message_Frame[id], false)
            set Message_Timer[id] = CreateTimer()
            if GetLocalPlayer() == Player(id) then
                call BlzFrameSetVisible(Message_Frame[id], true)
            endif
            set id = id + 1
        endloop
        call DestroyTimer(GetExpiredTimer())
    endfunction

    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.01, false, function CreateTextFrames)
    endfunction

    function CEM__SendMessage takes nothing returns nothing
        if udg_CEM__Player != Player(27) then
            call SendMessageToOnePlayer()
        else
            call ForForce(bj_FORCE_ALL_PLAYERS, function SendMessageToAllPlayers)
        endif
        set udg_CEM__Player = Player(27)
    endfunction

endlibrary
  • CustomErrorMessage Initialize
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- The actual text that will appear on screen: --------
      • Set VariableSet CEM__Text = <Empty String>
      • -------- --------
      • -------- How long the text remains on the screen: --------
      • Set VariableSet CEM__Duration = 4.00
      • -------- --------
      • -------- Multiplies the font size of the text by this much (gets pixelated at high values): --------
      • Set VariableSet CEM__Scale = 1.00
      • -------- --------
      • -------- Where the text appears horizontally on the screen confined between a range of 0.0 and 0.8: --------
      • Set VariableSet CEM__Position_X = 0.40
      • -------- --------
      • -------- Where the text appears vertically on the screen confined between a range of 0.0 and 0.6: --------
      • Set VariableSet CEM__Position_Y = 0.48
      • -------- --------
      • -------- Modifies the maximum width that the message can have before needing a return line: --------
      • Set VariableSet CEM__Max_Width = 0.20
      • -------- --------
      • -------- Modifies the maximum height that the message can have which influences the number of return lines: --------
      • Set VariableSet CEM__Max_Height = 0.10
      • -------- --------
      • -------- When set to Neutral Passive the message is sent to everyone. Otherwise, the message is only sent to the given player: --------
      • Set VariableSet CEM__Player = Neutral Passive
      • -------- Note: This variable automatically resets back to Neutral Passive after you send a message! --------
  • CustomErrorMessage Send
    • Events
    • Conditions
    • Actions
      • -------- Note: You could also run this action directly and skip needing to run this trigger: --------
      • Custom script: call CEM__SendMessage()
1720100021158.png

[STOP]

Here's the demonstration trigger that you can delete or disable once you understand how it works:
  • CEM Demo
    • Events
      • Player - Player 1 (Red) types a chat message containing <Empty String> as A substring
    • Conditions
    • Actions
      • Set VariableSet CEM__Text = (Entered chat string)
      • Set VariableSet CEM__Duration = (Random real number between 2.00 and 4.00)
      • Set VariableSet CEM__Scale = (Random real number between 1.00 and 2.00)
      • Set VariableSet CEM__Player = Player 1 (Red)
      • Trigger - Run CustomErrorMessage Send <gen> (ignoring conditions)
      • Wait 0.50 seconds
      • Set VariableSet CEM__Text = Player 1 shouldn't see this!
      • Set VariableSet CEM__Duration = 1.00
      • Set VariableSet CEM__Scale = 1.00
      • Set VariableSet CEM__Player = Player 2 (Blue)
      • Trigger - Run CustomErrorMessage Send <gen> (ignoring conditions)
This will display a custom error message equal to whatever Player 1 typed. This message uses a random duration and random scale to show the functionality of the system. I also send a second message to Player 2 after a short delay to prove that other player's error messages won't overwrite your own and that you can only see your own message.


Edit 1: I realized I forgot to make it MPI, it's been updated in this new version. Now you can define who receives the message using the CEM__Player variable. If you set this variable to Neutral Passive then the message will be sent to everyone. If you set it to say Player 3 it will only send the message to Player 3. Note that it will automatically default to Neutral Passive (everyone) if you leave the Player variable unset. Additionally, you can rely on the default values for the CEM variables (see Initialize trigger) instead of setting them yourself. In other words, if all of your messages will use a Scale of 1.5 and a Duration of 4.0 seconds then there's no need to Set these variables outside of the Initialize trigger. Just remember that they will use their last set value, with the exception of CEM__Player which is coded to automatically reset.

Edit 2: Renamed it to Custom Error Message since the previous title was misleading.
 

Attachments

  • Custom Error Message 1.w3m
    20.7 KB · Views: 3
Last edited:
Level 9
Joined
Mar 17, 2016
Messages
151
Actually now that I'm at it, is there a way to insert a variable into the Default Upkeep UI spot at the top right of the screen?
For instance, I have a variable that tracks the rounds completed, I'm wondering if I can insert that number to show the players the current round that would change when the variable itself is changed. No need for any pop-up message, rather just a clean visible counter. Is this possible?
I tried just putting (udg_MyVariable) into the game interface upkeep message but it just displays as text. Not sure if I just don't know the right code to do it, or if it's impossible.
 

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,371
Actually now that I'm at it, is there a way to insert a variable into the Default Upkeep UI spot at the top right of the screen?
For instance, I have a variable that tracks the rounds completed, I'm wondering if I can insert that number to show the players the current round that would change when the variable itself is changed. No need for any pop-up message, rather just a clean visible counter. Is this possible?
I tried just putting (udg_MyVariable) into the game interface upkeep message but it just displays as text. Not sure if I just don't know the right code to do it, or if it's impossible.
If you look into the code of my library you can see where it's creating the text frame. It's under a function called CreateFrames(), you just need to extract the important functions and variables from it. This should work:
vJASS:
library RoundText initializer Init

    globals
        private framehandle Text_Frame
    endglobals

    private function CreateTextFrame takes nothing returns nothing
        set Text_Frame = BlzCreateFrameByType("TEXT", "MyTextFrame", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
        call BlzFrameSetScale(Text_Frame, 1)
        call BlzFrameSetAbsPoint(Text_Frame, FRAMEPOINT_CENTER, 0.4, 0.48)
        call BlzFrameSetSize(Text_Frame, 0.3, 0.1)
        call BlzFrameSetTextAlignment(Text_Frame, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_CENTER)
        call BlzFrameSetText(Text_Frame, "Round: 0")
        call BlzFrameSetEnable(Text_Frame, false)
        call BlzFrameSetVisible(Text_Frame, true)
        call DestroyTimer(GetExpiredTimer())
    endfunction

    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.01, false, function CreateTextFrame)
    endfunction

    function RoundText_Update takes string s returns nothing
        call BlzFrameSetText(Text_Frame, s)
    endfunction
endlibrary
Here's how you can use the above code:
  • Actions
    • Set Variable MyString = ("Round: " + (Integer(MyRoundVariable))
    • Custom script: call RoundText_Update(udg_MyString)
Edit: Fixed a typo in the code.
 
Last edited:
Level 9
Joined
Mar 17, 2016
Messages
151
Just for the sake of clarification, this is what I was meaning to edit, is what you posted here to do that? Or is it to make on-screen text like before?

I ask because I think if this method doesn't work, my easier way would just to do it using a Leaderboard/Multiboard since i know nothing about Jass
1720135440296.png
 

Uncle

Warcraft Moderator
Level 70
Joined
Aug 10, 2018
Messages
7,371
Ah, I misread, but you can still use what I suggested. I have a map where I hid the default upkeep frame and placed my own custom UI text over it.

Here's how it's done.
vJASS:
library RoundText initializer Init

    globals
        private framehandle Text_Frame
    endglobals

    private function HideUIByName takes string name, integer index returns nothing
        local framehandle fh = BlzGetFrameByName(name, index)
        call BlzFrameClearAllPoints(fh)
        call BlzFrameSetAbsPoint(fh, FRAMEPOINT_CENTER, 3, 3)
        call BlzFrameSetScale(fh, 0.001)
        set fh = null  
    endfunction

    private function CreateTextFrame takes nothing returns nothing

        // Hide the original upkeep frame
        call HideUIByName("ResourceBarUpkeepText", 0)

        // Create a new text frame there
        set Text_Frame = BlzCreateFrameByType("TEXT", "MyTextFrame", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
        call BlzFrameSetScale(Text_Frame, 1)
        call BlzFrameSetAbsPoint(Text_Frame, FRAMEPOINT_CENTER, 0.76, 0.589)
        call BlzFrameSetEnable(Text_Frame, false)
        call BlzFrameSetVisible(Text_Frame, true)
        call BlzFrameSetLevel(Text_Frame, 1)
        call BlzFrameSetText(Text_Frame, "Round: 0")

        // Destroy the timer that ran this function
        call DestroyTimer(GetExpiredTimer())
    endfunction

    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.01, false, function CreateTextFrame)
    endfunction

    function RoundText_Update takes string s returns nothing
        call BlzFrameSetText(Text_Frame, s)
    endfunction

endlibrary
As you can see it's almost identical to my last post, just tweaked a few bits and pieces of code.
 

Attachments

  • Custom Corner Upkeep Text 1.w3m
    17.9 KB · Views: 1
Top