Skip to content

Instantly share code, notes, and snippets.

@Tey
Last active October 21, 2021 11:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Tey/7aff5cb5d7caaa7f49ee820887675eb5 to your computer and use it in GitHub Desktop.
Save Tey/7aff5cb5d7caaa7f49ee820887675eb5 to your computer and use it in GitHub Desktop.
[WoT] Colorize in battle chat with XVM colors
{
// Add the following line to @xvm.rc to register that file:
// , "chat": ${"chat.xc":"chat"}
// Color values for substitutions.
// Значения цветов для подстановок.
"def": {
// Dynamic color by various statistical parameters.
// Динамический цвет по различным статистическим показателям.
"colorRating": {
"very_bad": "0xFE0E00", // very bad / очень плохо
"bad": "0xFE7903", // bad / плохо
"normal": "0xF8F400", // normal / средне
"good": "0x459300", // good / хорошо
"very_good": "0x02C9B3", // very good / очень хорошо
"unique": "0xD042F3" // unique / уникально
}
},
"chat": {
// WARNING: this does not use the XVM macros but Python string formating! The list of keys are:
// status, hip, cap, vehicleID, alive, ready, flag, wn6, squadnum, spo, xwgr, e, wn8, r, xwn8,
// lang, xeff, b, frg, nm, dmg, xwn6, xr, name, winrate, w, wgr, lvl, team, def, cr (color), xte
// Colorize the name of message author if true
"colorizeAuthor": true,
// Colorize the name of targets in messages if true
"colorizeTarget": true,
// Use color scale from "colors" if set (use the ones from colors.xc otherwise)
"customColors": false,
// Dynamic color for XVM Scale
// Динамический цвет по шкале XVM
// http://www.koreanrandom.com/forum/topic/2625-/
"colors": [
{ "value": 16.5, "color": ${"def.colorRating.very_bad" } }, // 00 - 16.5 - very bad (20% of players)
{ "value": 33.5, "color": ${"def.colorRating.bad" } }, // 16.5 - 33.5 - bad (better than 20% of players)
{ "value": 52.5, "color": ${"def.colorRating.normal" } }, // 33.5 - 52.5 - normal (better than 60% of players)
{ "value": 75.5, "color": ${"def.colorRating.good" } }, // 52.5 - 75.5 - good (better than 90% of players)
{ "value": 92.5, "color": ${"def.colorRating.very_good"} }, // 75.5 - 92.5 - very good (better than 99% of players)
{ "value": 999, "color": ${"def.colorRating.unique" } } // 92.5 - XX - unique (better than 99.9% of players)
],
// Prefix for the message author
"authorPrefix": "<img src='xvm://res/icons/flags/{flag}.png' width='16' height='13'>",
// Prefix for target players
"prefix": "",
// Suffix for the message author
"authorSuffix": "",
// Suffix for target players
"suffix": "",
// Log level: -1=NONE, 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG
"logLevel": 0
}
}
import re
import string
from debug_utils import _doLog
from messenger.gui.Scaleform.channels.bw_chat2.battle_controllers import TeamChannelController
from messenger.formatters.chat_message import TeamMessageBuilder
from helpers import dependency
from skeletons.gui.battle_session import IBattleSessionProvider
from messenger_common_chat2 import MESSENGER_ACTION_IDS
from xvm_main.python.stats import _stat
import xvm_main.python.utils as xvm_utils
import xvm_main.python.config as config
def dump(obj):
values = []
for name in dir(obj):
attr = getattr(obj, name)
if callable(attr):
if name.startswith('get') or name.startswith('is'):
values.append('%s=%s' % (name, attr()))
return '[%s]' % (', '.join(values))
def LOG_ERROR(msg, *kargs, **kwargs):
if config.get('chat/logLevel', 0) >= 0:
_doLog('ERROR', msg, kargs, kwargs)
def LOG_WARN(msg, *kargs, **kwargs):
if config.get('chat/logLevel', 0) >= 1:
_doLog('WARNING', msg, kargs, kwargs)
def LOG_INFO(msg, *kargs, **kwargs):
if config.get('chat/logLevel', 0) >= 2:
_doLog('INFO', msg, kargs, kwargs)
def LOG_DEBUG(msg, *kargs, **kwargs):
if config.get('chat/logLevel', 0) >= 3:
_doLog('DEBUG', msg, kargs, kwargs)
LOG_DEBUG('LOADING')
# http://stackoverflow.com/a/19800610/5099839
class StatsFormatter(string.Formatter):
def __init__(self, default=0):
self.default = default
def get_value(self, key, args, kwargs):
if isinstance(key, str):
v = kwargs.get(key, self.default)
return 0 if v is None else v
else:
Formatter.get_value(key, args, kwargs)
class ChatColor(object):
sessionProvider = dependency.descriptor(IBattleSessionProvider)
# PlayerName_re matches "Name (Vehicle)", "Name[CLAN] (Vehicle)", and "Name[CLAN] (Vehicle (x))"
PlayerName_re = re.compile(r'([^\s><\[]+)(\[[^\]]*\])?( \((?:[^\(\)]|\([^\)]*\))+\))')
def __init__(self):
LOG_DEBUG('ChatColor.__init__()')
ChatColor._TeamChannelController_formatCommand = TeamChannelController._formatCommand
TeamChannelController._formatCommand = ChatColor.TeamChannelController_formatCommand
ChatColor._TeamChannelController_formatMessage = TeamChannelController._formatMessage
TeamChannelController._formatMessage = ChatColor.TeamChannelController_formatMessage
# Hooking TeamMessageBuilder.setColors() is not enough to set the color of enemy target
#ChatColor._TeamMessageBuilder_setColors = TeamMessageBuilder.setColors
#TeamMessageBuilder.setColors = ChatColor.TeamMessageBuilder_setColors
@staticmethod
def getVehIDByPlayerName(playerName):
# FIXME: build a map during battle startup and stop iterating every time
for vo in ChatColor.sessionProvider.getArenaDP().getVehiclesInfoIterator():
if vo.player.name == playerName:
return vo.vehicleID
return None
@staticmethod
def getPlayerStats(vehicleID):
# Retrieve player rating and associated color
# FIXME: is there any API to retrieve the cached stats from Python!?
if vehicleID in _stat.players:
pl = _stat.players.get(vehicleID)
cacheKey = "%d=%d" % (pl.accountDBID, pl.vehCD)
if cacheKey in _stat.cacheBattle:
return ChatColor.fixStats(_stat.cacheBattle[cacheKey])
return None
@staticmethod
def fixStats(stats):
if stats is None:
return None
stats = stats.copy()
rating = config.networkServicesSettings.rating
if not stats.has_key('xte') and stats.has_key('v') and stats['v'].has_key('xte'):
stats['xte'] = stats['v']['xte']
stats['r'] = stats.get(rating, 0)
stats['xr'] = stats['r'] if rating.startswith('x') else stats.get('x' + rating, 0)
stats['cr'] = ChatColor.getRatingColor(stats)
# Stats for new players are incomplete, so use default values (StatsFormatter does most of the job)
stats['flag'] = stats.get('flag', 'default')
return stats
@staticmethod
def getRatingColor(stats):
if stats is None:
return None
rating = config.networkServicesSettings.rating
v = stats.get(rating, 0)
if config.get('chat/customColors', False):
# Use custom colors from chat.xc
v = stats.get('x' + rating, v) # normalized rating only
colors = config.get('chat/colors', None)
if colors is None:
color = ''
else:
color = next((int(x['color'], 0) for x in colors if v <= float(x['value'])), 0xFFFFFF)
color = "#{0:06x}".format(color)
else:
# Use colors from colors.xc
color = xvm_utils.getDynamicColorValue('x' if rating.startswith('x') else rating, v if v is not None else 0)
LOG_DEBUG('%s/%s => %s' % (rating, v, color))
return color
@staticmethod
def buildExtra(stats, name):
if stats is None:
return ''
extra = config.get('chat/%s' % name, '')
return xvm_utils.fixImgTag(StatsFormatter().format(extra, **stats))
@staticmethod
def colorize(msg, cmd=None):
if cmd is not None:
# TODO: permit to add prefix/suffix to command message, and change color of message
pass
res = u''
pos = 0
is_author = True
for m in ChatColor.PlayerName_re.finditer(msg):
res += msg[pos:m.start()]
pos = m.end()
name = m.group(1)
clan = m.group(2)
vname = m.group(3)
repl = m.group(0)
vid = ChatColor.getVehIDByPlayerName(name)
if vid is None:
LOG_WARN('Cannot find VID for player with name "%s"' % name)
else:
stats = ChatColor.getPlayerStats(vid)
if stats is None:
LOG_ERROR('Cannot find stats for player with name "%s"' % name)
else:
color = stats['cr']
prefix = ChatColor.buildExtra(stats, 'authorPrefix' if is_author else 'prefix')
suffix = ChatColor.buildExtra(stats, 'authorSuffix' if is_author else 'suffix')
colorize = config.get('chat/%s' % ('colorizeAuthor' if is_author else 'colorizeTarget'), True)
if colorize:
repl = "%s<font color='%s'>%s%s%s</font>%s" % (prefix, color, name, clan if clan is not None else '', vname, suffix)
else:
repl = "%s%s%s%s%s" % (prefix, name, clan if clan is not None else '', vname, suffix)
res += repl
is_author = False
res += msg[pos:]
LOG_DEBUG("'%s' => '%s'" % (msg, res))
return res
@staticmethod
def TeamChannelController_formatCommand(self, command):
fmt = ChatColor._TeamChannelController_formatCommand(self, command)
# LOG_DEBUG('TeamChannelController_formatCommand(%s) using %s => %s' % (command, self._mBuilder, fmt))
cmd = MESSENGER_ACTION_IDS.battleChatCommandFromActionID(command.getID())
if cmd is not None:
cmd = dict(cmd_name=cmd.msgText, cmd_id=command.getID())
# LOG_DEBUG(cmd)
return (fmt[0], ChatColor.colorize(fmt[1], cmd))
@staticmethod
def TeamChannelController_formatMessage(self, message, doFormatting=True):
fmt = ChatColor._TeamChannelController_formatMessage(self, message, doFormatting)
# LOG_DEBUG('TeamChannelController_formatMessage(%s) using %s => %s' % (message, self._mBuilder, fmt))
return (fmt[0], ChatColor.colorize(fmt[1]))
@staticmethod
def TeamMessageBuilder_setColors(self, dbID):
ChatColor._TeamMessageBuilder_setColors(self, dbID)
vehicleID = ChatColor.sessionProvider.getArenaDP().getVehIDByAccDBID(dbID)
color = ChatColor.getPlayerRatingColor(vehicleID)
if color is not None:
self._ctx['playerColor'] = color[1:] # remove '#' prefix
return self
ChatColor()
LOG_DEBUG('LOADED')
@Tey
Copy link
Author

Tey commented Jan 29, 2017

Fixed a bug with players with no stats (new players)

@Tey
Copy link
Author

Tey commented Feb 3, 2017

Added 'cr' key for color rating (equivalent to "{{c:r}}" XVM macro).

@Tey
Copy link
Author

Tey commented Feb 4, 2017

Fixed a bug where colorizeTarget was not taken into account (Python code expected colorize instead)

@Tey
Copy link
Author

Tey commented Feb 6, 2017

Fix for xTE rating.
Added a way to use specific colors instead of the ones from colors.xc.

@Tey
Copy link
Author

Tey commented Feb 8, 2017

Added a way to set mod verbosity.

@Aslain
Copy link

Aslain commented Aug 26, 2021

There is a bug that is causing the flag to be displayed twice: https://iv.pl/images/218b8259684026908ef014e57b55517d.jpg
Please fix :)

@Tey
Copy link
Author

Tey commented Aug 30, 2021

@Aslain Sorry, I haven't played this game for years and it's not even installed anymore on my computers, so I will not be able to provide updates/fixes. However, a quick look at the code makes me believe there might be another mod adding the first flag, because if mod_chat_color would duplicate the flag, then it would also duplicate the player name which does not seem to be the case in your screenshot.

@Aslain
Copy link

Aslain commented Aug 31, 2021

@Aslain Sorry, I haven't played this game for years and it's not even installed anymore on my computers, so I will not be able to provide updates/fixes. However, a quick look at the code makes me believe there might be another mod adding the first flag, because if mod_chat_color would duplicate the flag, then it would also duplicate the player name which does not seem to be the case in your screenshot.

Ok, thanks for your response, the mod was checked with default XVM + mod_chat_color and it was double flags at this point. So no other mod was involed.
I have disabled the flags in config, nobody is using them anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment