Questions related to modifying HUD in Lua

Status
Not open for further replies.

WellDoneSnake

Yoko Shimomura
Hi guys,

I've recently started to learn Lua and have been fiddling with other Lua scripts from releases in order to help me learn more about the programming language.

A few days ago I created a script that awards players who have a certain amount of rings on them (for example, if the player has less than 15 rings on them, they shall be rewarded with +10 rings upon touching a starpost).
*I used the starpost script from FuriousFox's "Bonus Stage wad" as a template*

Next I decided that the player should be given a visual cue for what they have been awarded with, upon touching a starpost.
I decided that I'll mimic the effect of the visual cue seen in the "Shadow the Hedgehog" video game when touching a checkpoint: 'where the item icon is displayed for a brief moment before fading away'.
As seen here (0:30 to 0:34).

After learning about drawing HUDs from Monster psychic cat's tutorial, I added a script that would show an icon which corresponds with the award that the player received after touching the starpost.

That's when I bumped into a dilemma.

I could make the visual cue appear but I couldn't think of a way to make it disappear soon after.

Because of this I spent quite some time trying to experiment with different functions, flags, etc. that I read about from SRB2's wiki pages.
But I had no luck, after taking a look at the "timer" scripts in FuriousFox's Bonus Stage wad and Inuyasha's "lua adventure items wad", I decided I would try to create a similar timer to those two scripts.
But unfortunately my knowledge on custom timers isn't that great, and I am unsure of what conditions to set in order to end/stop the the visual cues' appearance.

TL;DR
I made a script that awards players (who have a certain amount of rings) with an item. I wanted to have an extra visual cue that shows what item the player has received in the form if an icon to the top right corner of the screen.
However my knowledge of Lua scripting is limited at the moment. I'd like to know if there is a way to make this new HUD disappear after a few seconds in a fading like manner.

Extra questions:
- Is there a way to assign custom HUDs to the "hud.enable/hud.disable" functions?
- Can custom images HUDs disappear at all? (After being called forth. *Excluding mapload/mapchange*)
- Can you change the visibility of a custom HUD after it has already been set to have a visibility? E.g. If a custom HUD is already opaque once loaded, can it be made translucent via video flags.

--------------------------------------------------
Codes
--------------------------------------------------
1) 0 to 14 rings = +10 rings 2) 15 to 39 rings = +20 rings 3) 40 to 59 rings = whirlwind shield 4) 60+ rings = forcefield shield
Code:
local function ItemReward(giver, receiver)
	if receiver.health <= 15 and receiver.player.starpostnum < giver.health
		P_GivePlayerRings(receiver.player, 10)
		S_StartSound(receiver.origin, 153, receiver.player)
	elseif receiver.health > 15 and receiver.player.starpostnum < giver.health
	and receiver.health <= 40
		P_GivePlayerRings(receiver.player, 20)
		S_StartSound(receiver.origin, 153, receiver.player)
	elseif receiver.health > 40 and receiver.player.starpostnum < giver.health
	and receiver.health <= 60 and receiver.player.powers[pw_shield] == SH_NONE
		receiver.player.powers[pw_shield] = SH_JUMP
		P_SpawnShieldOrb(receiver.player)
		S_StartSound(receiver.origin, 153, receiver.player)
	elseif receiver.health > 60 and receiver.player.starpostnum < giver.health
	and receiver.player.powers[pw_shield] == SH_NONE
		receiver.player.powers[pw_shield] = SH_FORCE|1
		P_SpawnShieldOrb(receiver.player)
		S_StartSound(receiver.origin, 153, receiver.player)
	end
end
addHook("TouchSpecial", ItemReward, MT_STARPOST)
Same as the first if statement in the example above but the visual effect that I want to occur, happens:
Code:
local function ItemReward(giver, receiver)
	if receiver.health < 15 and receiver.player.starpostnum < giver.health
		P_GivePlayerRings(receiver.player, 10)
		S_StartSound(receiver.origin, 153, receiver.player)
		local function giftdraw(v, stplyr)
			if stplyr.mo
				v.draw(304, 23, v.cachePatch("SRBXB0"))
			end
		end
		hud.add(giftdraw)
	end
end
addHook("TouchSpecial", ItemReward, MT_STARPOST)
*The emblem sound is placeholder, I thought it'd be nice to hear that you "something" as well.*
 
What you can do is give the player a timer (something like player.icontimer or something) and a variable to hold the text you want to display (like player.icontext maybe).
The timer would always decrement by 1 as long as it is above 0.

When you have to display text, you set player.icontext to the text you want, and player.icontimer to the amount of time, in tics, that this text should be displayed.

Then in the HUD hook you check that the timer is above 0, and if it is, draw stuff.
In there, make a local variable and name it something like "transp" (make sure it starts with a value of 0), then make icontimer checks and set transp to the transparency flag you want depending on how much time is left in the icon timer.
Then in the drawing flags, add transp as another flag.

- Is there a way to assign custom HUDs to the "hud.enable/hud.disable" functions?
No, there is not.
Nothing is going to be drawn if the custom HUD hook does not have anything to draw, though (maybe because of checks and the like).

- Can custom images HUDs disappear at all? (After being called forth. *Excluding mapload/mapchange*)
Yes. The script must not call the drawing function for it to not be drawn.
All text and images will only be drawn for 1 tic if the function is called in the same tic. They must be continuously called for the drawing to stay.

- Can you change the visibility of a custom HUD after it has already been set to have a visibility? E.g. If a custom HUD is already opaque once loaded, can it be made translucent via video flags.
Yes, that can be done. The explanation is written above.




Sorry if I wrote a mess instead of an explanation.
 
Last edited:
Code:
local function ItemReward(giver, receiver)
	if receiver.health < 15 and receiver.player.starpostnum < giver.health
		P_GivePlayerRings(receiver.player, 10)
		S_StartSound(receiver.origin, 153, receiver.player)
		[B]local function giftdraw(v, stplyr)
			if stplyr.mo
				v.draw(304, 23, v.cachePatch("SRBXB0"))
			end
		end
		hud.add(giftdraw)[/B]
	end
end
addHook("TouchSpecial", ItemReward, MT_STARPOST)

Generally, this part here (in bold) is bad pratice, as you're creating a new HUD object every time you are rewarded, which can eat some memory.

Like Rapidgame7 said, you can use separate variables to control when the graphic shows up, how long it takes, and what gets drawn.

Also, if you want to, translucency can be done by shifting up a variable (on player_t
like player.icontransp) by V_ALPHASHIFT (example: player.icontransp<<V_ALPHASHIFT - if icontransp is 5, it would give you V_TRANS50)

Pseudocode:

Code:
on every frame
   decrease display timer
   if timer is less than a second
      increase translucency variable
   end
end

when player is rewarded
     set timer (variable on [I]player_t[/I] like [I]player.icontimer[/I])
     set graphic and text (variables on [I]player_t[/I] like [I]player.icongfx[/I] and [I]player.icontext[/I])
     reset translucency​ variable ([I]player.icontransp[/I])
end

draw every frame
    if the display timer is higher than 0 then
           draw the text with translucency
           draw the icon with translucency
     end
end
 
Thanks for the responses, I've changed the script:
Code:
local icontimer = 0

local function startIconTimer()
	if icontimer != nil and icontimer > 0
		icontimer = $1 - 1
		if icontimer != nil and icontimer < TICRATE 
			icontransp = $1 + 1
		end
	end
end

local function ItemReward(giver, receiver)
	if receiver.health < 15 and receiver.player.starpostnum < giver.health
		P_GivePlayerRings(receiver.player, 10)
		S_StartSound(receiver.origin, 153, receiver.player)
		receiver.icontimer = 5*TICRATE
		receiver.icongift = iconpatch
		receiver.icontransp = 0
	end
end

local function hud_icon(v, stplyr)
	if stplyr.icontimer != nil and stplyr.icontimer < 0
		local icontransp = 0 <<V_ALPHASHIFT
		local iconpatch = v.cachePatch("SRBXB0")
			v.draw(304, 23, iconpatch, icontransp)
	end
end
hud.add(hud_icon)
addHook ("ThinkFrame", startIconTimer)
addHook("TouchSpecial", ItemReward, MT_STARPOST)

The player is still rewarded with an item although the icon doesn't display at all.
I can't work out what I'm missing and what I did incorrectly.
 
Code:
local function startIconTimer()
    if icontimer != nil and icontimer > 0
        icontimer = $1 - 1
        if icontimer != nil and icontimer < TICRATE 
            icontransp = $1 + 1
        end
    end
end
You're modifying a different variable. Should be player.icontimer:

Code:
local function startIconTimer()
      for player in players.iterate -- get player_t
       if player.icontimer != nil and player.icontimer > 0
        player.icontimer = $1 - 1
        if player.icontimer != nil    -- not neeeded, already checked before
                and player.icontimer < TICRATE 
            player.icontransp = $1 + 1
        end
          end
  end

Also, if player.icontransp is higher than 9, it will crash the game, you need to check that value before modifying it

Code:
if stplyr.icontimer != nil and [B]stplyr.icontimer < 0[/B]
Before, you set stplyr.icontimer to a value higher than 0, not less than 0



Code:
local icontransp = 0 <<V_ALPHASHIFT
local iconpatch = v.cachePatch("SRBXB0")
icontransp and iconpatch will always be the same, due to the way you've defined them here. Instead of 0<<V_ALPHASHIFT, should be stplyr.icontransp<<V_ALPHASHIFT

Also, iconpatch should be set in the reward code. Currently, the script set itemgift to a non-existent variable (iconpatch), which is set instead in the drawing code.

Code:
receiver.[B]player[/B].icontimer = 5*TICRATE
receiver.[B]player[/B].icongift = [B]v.cachePatch("SRBXB0")[/B]
receiver.[B]player[/B].icontransp = 0

[...]

local icontransp = stplyr.icontransp<<V_ALPHASHIFT
local iconpatch = stplyr.icongift
 
Last edited:
Alright I've made the changes that you recommended but line 21
Code:
receiver.player.icongift = v.cachePatch("SRBXB0")
seems to give me this warning upon touching a starpost:
"attempt to index global 'v' (a number value)".

Should I make another variable, where 'v.cachePatch("SRBX0")' is seen as a numerical value?
E.g. v.cachePatch("SRBX0") = # -- number
And then change line 21 to this 'receiver.player.icongift = # -- number that v.cachePatch("SRBX0") is'.
 
Oh, oops. v is not actually valid in ItemReward, it's instead a frame number. Instead, player.icongift can be the string "SRBXB0" and then, in the variable iconpatch (at function icon_hud), you use v.cachePatch with stplyr.icongift. (v.cachePatch(stplyr.icongift))
 
That's alright, so would the latter of what you said look like this:
'local iconpatch = v.cachePatch(stplyr.icongift)'
Edit: nvm I saw your edit, will try that and then tell you the results
 
Last edited:
Unfortunately the custom HUD still isn't showing.
I posted the whole code again below, so that you can see what's been edited. Since I'm not sure if the problem is related to the HUD function or another function.
Code:
local icontimer = 0

local function startIconTimer()
	for player in players.iterate
		if player.icontimer != nil and player.icontimer < 0
			if player.icontimer != nil and player.icontimer > 0
				player.icontimer = $1 - 1
				if player.icontimer < TICRATE 
					player.icontransp = $1 + 1
				end
			end
		end
	end
end

local function ItemReward(giver, receiver)
	if receiver.health < 15 and receiver.player.starpostnum < giver.health
		P_GivePlayerRings(receiver.player, 10)
		S_StartSound(receiver.origin, 153, receiver.player)
		receiver.player.icontimer = 5*TICRATE
		receiver.player.icongift = "SRBXB0"
		receiver.player.icontransp = 0
	end
end

local function hud_icon(v, stplyr)
	if stplyr.icontimer != nil and stplyr.icontimer < 0
		local icontransp = stplyr.icontransp<<V_ALPHASHIFT
		local iconpatch = (v.cachePatch("stplyr.icongift"))
			v.draw(304, 23, iconpatch, icontransp)
	end
end
hud.add(hud_icon)
addHook ("ThinkFrame", startIconTimer)
addHook("TouchSpecial", ItemReward, MT_STARPOST)
 
Code:
local function startIconTimer()
    for player in players.iterate
        if player.icontimer != nil and player.icontimer < 0
            if player.icontimer != nil and player.icontimer > 0
                player.icontimer = $1 - 1
                if player.icontimer < TICRATE 
                    player.icontransp = $1 + 1
                    -- check if icontransp is zero? higher values make the translucency loop
                end
            end
        end
    end
end
You're checking if player.icontimer is less than zero, while also checking if it's higher than zero.
Code:
-- remove the lines in bold:
local function startIconTimer()
    for player in players.iterate
        [B]if player.icontimer != nil and player.icontimer < 0[/B]
            if player.icontimer != nil and player.icontimer > 0
                player.icontimer = $1 - 1
                if player.icontimer < TICRATE 
                    player.icontransp = $1 + 1
                end
            end
        [B]end[/B]
    end
end
----------------

Code:
local iconpatch = (v.cachePatch("stplyr.icongift"))
iconpatch should be the variable stplyr.icongift, not a string:

Code:
local iconpatch =(v.cachePatch([B]stplyr.icongift[/B]))
 
Last edited:
Oh lol, I misread one your previous posts: "Also, if player.icontransp is higher than 9, it will crash the game, you need to check that value before modifying it..."
Thought that it said 'player.icontimer' instead 'player.icontransp' for some reason, hence why I tried to make it check.
I'll make edits again.
Edit: You crossed out what I quoted, should I not make it check then?
 
Last edited:
You crossed out what I quoted, should I not make it check then?

You could clear the timer if the translucency counter is higher than nine, or else it "rolls" back to default translucency and repeats.

Code:
[...]
if player.icontimer < TICRATE 
    player.icontransp = $1 + 1
    [B]if player.icontransp > 9
         player.icontimer = 0
     end[/B]
end
[...]
 
Last edited:
Alright thanks for the info, all the changes are up-to-date but there's still no icon being shown in the top right corner of the screen.

I decided to make another file for error testing, and had removed all timer elements to see if the hud will spawn without the use of a timer.

The results was this warning "bad argument #1 to 'cachePatch' (string expected, got nil)" for the line with local iconpatch variable.

I'm not sure if this is related to the problem (or even a reliable testing method), but I'm wondering if that "bad argument" is what's causing the original lua script to not display the icon/hud.
*No errors show up in the original file btw.*
 
TSDude said:
I'm not sure if this is related to the problem

It isn't, because the icon variables were not initialized.

How, exactly, are your hud_icon and startIconTimer functions set-up? Here's my version of the script (without ItemReward):

Code:
local function startIconTimer()
    for player in players.iterate
        if player.icontimer != nil and player.icontimer > 0
            player.icontimer = $1 - 1
            if player.icontimer < TICRATE 
                player.icontransp = $1 + 1
                if player.icontransp > 9 then
                    player.icontimer = 0
                end
            end
        end
    end
end

local function hud_icon(v, stplyr)
    if stplyr.icontimer != nil and stplyr.icontimer > 0
        local icontransp = stplyr.icontransp<<V_ALPHASHIFT
        local iconpatch = (v.cachePatch(stplyr.icongift))
            v.draw(304, 23, iconpatch, icontransp)
    end
end
 
Oh my, I forgot to change this line:
"if stplyr.icontimer != nil and stplyr.icontimer < 0"
to this
"if stplyr.icontimer != nil and stplyr.icontimer > 0"

It now works splendidly!

Thank you for taking your time to explain to me how the hud and timers work in conjunction with the item reward. I really appreciate it!

All I need to do now is lower the amount of seconds it stays on screen, which is pretty easy.
 
Protip: Use F9 to start/stop a GIF recording. It's like a shortcut to "startmovie" and "stopmovie".

That looks quite neat!
I'd advise that you put it at the bottom center of the screen though, because that spot is where the shield icon is located.
 
Status
Not open for further replies.

Who is viewing this thread (Total: 1, Members: 0, Guests: 1)

Back
Top