Source-Python-Dev-Team / Source.Python

This plugin aims to use boost::python and create an easily accessible wrapper around the Source Engine API for scripter use.
http://forums.sourcepython.com
GNU General Public License v3.0
164 stars 33 forks source link

HudMsg overlapping in color #488

Open Frag1337 opened 1 year ago

Frag1337 commented 1 year ago

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

Ayuto commented 1 year ago

Can you provide a snippet to reproduce the issue?

Frag1337 commented 1 year ago

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.

jordanbriere commented 1 year ago

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).

Frag1337 commented 1 year ago

Interesting.

Could you provide an example snippet to try out?

jordanbriere commented 1 year ago

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()
Frag1337 commented 1 year ago

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.