Source.Python icon indicating copy to clipboard operation
Source.Python copied to clipboard

HudMsg overlapping in color

Open Frag1337 opened this issue 2 years ago • 6 comments

Hey there,

I found a problem with HudMsg.

I use HudMsg to show players some information in an interval, and additionally define different colors for the text. Lets take Pink as an example. The color seems to turn plain white real quick because I think it mixes with the color from previous messages. The weird thing is, even though the text displays fine, the color doesn't show as expected.

I'm not sure if this is a bug or not, but I've seen other servers using the same thing with the color working correctly. Maybe Sourcemod does something to reset it before sending each message?

Thanks

Frag1337 avatar Jul 18 '23 18:07 Frag1337

Can you provide a snippet to reproduce the issue?

Ayuto avatar Jul 18 '23 19:07 Ayuto

Sure,

I'm currently not at home, but I tried to write something which should hopefully reproduce it.

from colors import Color
from filters.players import PlayerIter
from listeners.tick import Repeat
from messages import HudMsg

HUD_TIMER_INTERVAL = 0.1
HUD_COLOR = Color(255, 105, 105, 255)  # Red


def on_hud_update():
    message = "Hello World!"

    for player in PlayerIter("alive"):
        HudMsg(message, -1.0, 1.0, HUD_COLOR, channel=1).send(player.index)


hud_timer = Repeat(on_hud_update)
hud_timer.start(HUD_TIMER_INTERVAL)

Some colors work as expected, but some just turn into plain white due to the overlapping. You see it in realtime if you adjust the HUD_TIMER_INTERVAL to 1.0, and once you spawn in, you see it getting brighter each second.

Frag1337 avatar Jul 18 '23 20:07 Frag1337

To negate the blending effect applied by the client:

HUD_COLOR = Color(255 >> 2, 105 >> 2, 105 >> 2, 255)

However, you would need to stack 16 of them first (can be empty strings) in order to reach the desired render in that specific channel. This can be done once per round (after ResetHUD has been sent).

jordanbriere avatar Jul 19 '23 05:07 jordanbriere

Interesting.

Could you provide an example snippet to try out?

Frag1337 avatar Jul 19 '23 15:07 Frag1337

Interesting.

Could you provide an example snippet to try out?

# ../addons/source-python/timeleft/timeleft.py

"""Simple plugin that display the timeleft above the scoreboard."""

# ============================================================================
# >> IMPORTS
# ============================================================================
# Python Imports
#   DateTime
from datetime import timedelta
#   Sys
from sys import maxsize

# Source.Python Imports
#   Colors
from colors import Color
#   Core
from core.cache import cached_property
#   Cvars
from cvars import ConVar
#   Engines
from engines.gamerules import find_game_rules
from engines.server import engine_server
from engines.server import global_vars
#   Entities
from entities.dictionary import SyncedEntityDictionary
#   Filters
from filters.entities import EntityIter
#   Listeners
from listeners import ButtonStatus
from listeners import get_button_combination_status
from listeners import OnButtonStateChanged
from listeners.tick import RepeatStatus
#   Memory
from memory import get_virtual_function
from memory.hooks import hooks_disabled
from memory.hooks import PostHook
#   Messages
from messages import get_message_index
from messages import HudMsg
import messages.hooks
#   Players
from players.entity import Player
from players.constants import PlayerButtons


# ============================================================================
# >> GLOBALS
# ============================================================================
HUD_PARAMS = {
    'x': -1.0,
    'y': -2.0,
    'channel': 1,
    'color1': Color(255 >> 2, 105 >> 2, 105 >> 2, 255),
    'hold_time': maxsize
}

EMPTY_HUDMSG = HudMsg('', **HUD_PARAMS)
TIMELEFT_HUDMSG = HudMsg('\nTime Remaining: {timeleft}', **HUD_PARAMS)

mp_timelimit = ConVar('mp_timelimit')
RESETHUD_MESSAGE_INDEX = get_message_index('ResetHUD')


# ============================================================================
# >> CLASSES
# ============================================================================
class Player(Player):
    caching = True

    def reset(self):
        with hooks_disabled():
            for _ in range(16):
                EMPTY_HUDMSG.send(self.index)

    def send(self):
        timelimit = mp_timelimit.get_int()
        if timelimit < 1:
            timeleft = '* No Time Limit *'

        else:
            timeleft = (
                find_game_rules().get_property_float('cs_gamerules_data.m_flGameStartTime')
                + timelimit
                * 60
            ) - global_vars.current_time

            if timeleft <= 0:
                timeleft = '* Last Round *'
            else:
                timeleft = str(timedelta(seconds=timeleft)).rsplit('.')[~1]

        TIMELEFT_HUDMSG.send(self.index, timeleft=timeleft)

    @cached_property
    def repeat(self):
        return super().repeat(self.send)

    def enable(self):
        repeat = self.repeat
        if repeat.status is RepeatStatus.RUNNING:
            return

        repeat.start(1, execute_on_start=True)

    def disable(self):
        repeat = self.repeat
        if repeat.status is not RepeatStatus.RUNNING:
            return

        repeat.stop()
        EMPTY_HUDMSG.send(self.index)


class Players(SyncedEntityDictionary):
    def on_automatically_created(self, index):
        self[index].reset()

players = Players(Player, EntityIter('player'))


# ============================================================================
# >> HOOKS
# ============================================================================
@PostHook(get_virtual_function(engine_server, 'MessageEnd'))
def _(stack_data, return_value):
    data = messages.hooks._user_message_data
    if data is None or data[0] is not RESETHUD_MESSAGE_INDEX:
        return

    for player in map(Player, messages.hooks._recipients):
        player.delay(0, player.reset)


# ============================================================================
# >> LISTENERS
# ============================================================================
@OnButtonStateChanged
def _(player, old, new):
    status = get_button_combination_status(old, new, PlayerButtons.SCORE)
    if status is None:
        return

    player = Player(player.index)
    if status == ButtonStatus.PRESSED:
        player.enable()
    else:
        player.disable()

jordanbriere avatar Jul 19 '23 20:07 jordanbriere

Thank you so much. This actually worked and the color is now showing as intended. Looks like this is not something Source.Python can fix, so this can be safely closed. Sorry about opening an issue.

Frag1337 avatar Jul 19 '23 22:07 Frag1337