-
-
Save adamcunnington/2c6840502ddcacae37e7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# <path to game directory>/addons/eventscripts/_engines/python/Lib/esutils/ | |
# players.py | |
# by Adam Cunnington | |
"""Provide a simple API to access player attributes in a single uniform | |
manner. Also provide a highly versatile filter system to get a set of player | |
objects. | |
""" | |
import itertools | |
import functools | |
import math | |
from esutils import tools, weapons | |
import es | |
__all__ = ( | |
"UNASSIGNED", | |
"SPECTATOR", | |
"TERRORIST", | |
"COUNTER_TERRORIST", | |
"STEAM_PENDING", | |
"PlayersError", | |
"Player", | |
"all_players", | |
"get_player_from_handle", | |
"get_player_from_name", | |
"get_player_from_steam_ID", | |
) | |
_EXISTS_USER_ID = "userid" | |
# To avoid passing meaningless integers throughout the library and usage, | |
# declare them once as constants. | |
UNASSIGNED = 0 | |
SPECTATOR = 1 | |
TERRORIST = 2 | |
COUNTER_TERRORIST = 3 | |
STEAM_PENDING = "Steam_ID_PENDING" | |
class PlayersError(Exception): | |
"""General error encountered relating to esutils.players""" | |
class _PlayerAttribute(object): | |
"""A common player information type that is obtained through | |
es.createplayerlist(<userid>)[<userid>]. These items are read-only. | |
""" | |
def __init__(self, name): | |
"""Instantiate a _PlayerAttribute object. | |
Arguments: | |
name - the key value for the information item. | |
""" | |
self.name = name | |
def get(self, player): | |
"""Return the value of the player's attribute. If it has been obtained | |
before and the attribute values don't require updating, return a | |
cached value for speed. This method should generally only be | |
implicitly called by python. | |
Arguments: | |
player - the relevant player object. | |
""" | |
if player._attributes is None or player.auto_update_attributes: | |
player.update_attributes() | |
return player._attributes[self.name] | |
class _PlayerEntity(object): | |
"""A common player information type that is obtained | |
es.getplayerprop(<userid>, <entity property>). These items can be writable | |
as well as readable. | |
""" | |
def __init__(self, name): | |
"""Instantiate a _PlayerAttribute object. | |
Arguments: | |
name - the full entity property name for the information item. | |
""" | |
self.name = name | |
def get(self, user_ID): | |
"""Return the value of the player's entity property. Cacheing is not | |
possible and it's also unnecessary as es.getplayerprop is fast. This | |
method should generally only be implicitly called by python. | |
Arguments: | |
user_ID - the relevant user ID. | |
""" | |
return es.getplayerprop(user_ID, self.name) | |
def set(self, user_ID, value): | |
"""Set the value of the player's entity property. This | |
method should generally only be implicitly called by python. | |
Arguments: | |
user_ID - the relevant user ID. | |
value - the new value to set the property at. | |
""" | |
es.setplayerprop(user_ID, self.name, value) | |
def _address_transformer(value): | |
hostname, port = value.split(":") | |
return hostname, int(port) | |
def _bit_transformer(value): | |
return bool(value & 1) | |
def _frozen_transformer(value): | |
return value not in (2, 4, 8) | |
def _get_colour(player): | |
return tools.get_colour(player.index) | |
def _get_eye_angles(user_ID): | |
return (es.getplayerprop(user_ID, "CCSPlayer.m_angEyeAngles[%s]" % index) | |
for index in range(2)) | |
def _get_eye_location(player): | |
for index, coordinate in enumerate(player.location): | |
yield es.getplayerprop(player.user_ID, | |
"CBasePlayer.localdata.m_vecViewOffset[%s]" | |
% index) + coordinate | |
def _get_flame_indexes(player): | |
for index in es.createentityindexlist("entityflame"): | |
ownership = es.getindexprop(index, "CEntityFlame.m_hEntAttached") | |
if ownership == player.handle: | |
yield index | |
def _get_index(player): | |
return es.getindexfromhandle(player.handle) | |
def _get_language(user_ID): | |
return es.getclientvar(user_ID, "cl_language") | |
def _get_knife(player): | |
return _get_player_weapon(player.handle, 3) | |
def _get_player_weapon(handle, slot): | |
for index in weapons.indexes_by_slot(slot): | |
if es.getindexprop(index, "CBaseCombatWeapon.m_hOwner") == handle: | |
return weapons.Weapon(index) | |
return None | |
def _get_primary(player): | |
return _get_player_weapon(player.handle, 1) | |
def _get_secondary(player): | |
return _get_player_weapon(player.handle, 2) | |
def _get_vector(user_ID): | |
return (es.getplayerprop(user_ID, | |
"CBasePlayer.localdata.m_vecVelocity[0]"), | |
es.getplayerprop(user_ID, | |
"CBasePlayer.localdata.m_vecVelocity[1]"), | |
es.getplayerprop(user_ID, | |
"CBasePlayer.localdata.m_vecVelocity[2]")) | |
def _get_view_angle(user_ID): | |
eye_angle0, eye_angle1 = _get_eye_angles(user_ID) | |
if eye_angle1 < 0: | |
eye_angle1 += 360 | |
roll = es.getplayerprop(user_ID, "CBaseEntity.m_angRotation").split(",")[2] | |
return (eye_angle0, eye_angle1, float(roll)) | |
def _get_view_coordinates(user_ID): | |
model = "props_c17/tv_monitor01_screen.mdl" | |
es.server.cmd("es_xprop_dynamic_create %s %s" %(user_ID, model)) | |
last_give = int(es.ServerVar("eventscripts_lastgive")) | |
location = es.getindexprop(last_give, "CBaseEntity.m_vecOrigin") | |
es.server.insertcmd("es_xremove %s" % last_give) | |
return location | |
def _get_view_vector(user_ID): | |
eye_angle0, eye_angle1 = _get_eye_angles(user_ID) | |
eye1 = math.radians(eye_angle1) | |
return (math.cos(eye1), math.sin(eye1), | |
math.sin(math.radians(eye_angle0)) * -1) | |
def _get_weapon(player): | |
if player._attributes is None or player.auto_update_attributes: | |
player.update_attributes() | |
weapon = player._attributes["weapon"] | |
for index in list(weapons.weapon_types_by_name(weapon)).indexes: | |
ownership = es.getindexprop(index, "CBaseCombatWeapon.m_hOwner") | |
if ownership == player.handle: | |
return weapons.Weapon(index) | |
return None | |
def _has_c4(player): | |
for index in weapons.weapon_types_by_name("weapon_c4").indexes: | |
ownership = es.getindexprop(index, "CBaseCombatWeapon.m_hOwner") | |
if ownership == player.handle: | |
return True | |
return False | |
def _immortal_transformer(value): | |
return value == 0 | |
def _jetpack_transformer(value): | |
return value == 4 | |
def _is_burning(player): | |
return bool(_get_flame_indexes(player)) | |
def _is_connected(user_ID): | |
"""Return whether or not a given unique session ID is active. | |
Arguments: | |
user_ID - the unique session ID of the user. | |
""" | |
return bool(es.exists(_EXISTS_USER_ID, user_ID)) | |
def _is_validated(user_ID): | |
"""Return whether ot not a given user's network ID has been validated by | |
the server. | |
Arguments: | |
user_ID - the unique session ID of the user. | |
""" | |
return es.getplayersteamid(user_ID) != STEAM_PENDING | |
def _modulus_transformer(value): | |
return bool(value % 2) | |
def _no_block_transformer(value): | |
return value == 2 | |
def _no_clip_transformer(value): | |
return value == 8 | |
def _player_property(getter, get_transformer=None, pass_instance_get=False, | |
setter=None, set_transformer=None, | |
pass_instance_set=False): | |
"""Create an instance property representing specific information about a | |
player. Common information types include player attributes and player | |
entity propeties but custom items are also possible. | |
Arguments: | |
getter - the callable to be used to return the value when the property is | |
queried. | |
get_transformer (Keyword Default: None) - a callable that return values | |
pass through before they reach the user. This should be predominantly used | |
to ensure the end type is valid by raising an exception if not. | |
pass_instance_get (Keyword Default: False) - whether or not the player | |
instance should be passed to the getter callable instead of the user's ID. | |
setter (Keyword Default: None) - the callable to be used to set the value | |
when the property is queried. | |
set_transformer (Keyword Default: None) - a callable that return values | |
pass through before they reach the setter callable. This should be | |
predominantly used to ensure the end type is valid by raising an exception | |
if not. | |
pass_instance_set (Keyword Default: False) - whether or not the player | |
instance should be passed to the setter callable instead of the user's ID. | |
""" | |
def get(self): | |
if not pass_instance_get: | |
parameter = self.user_ID | |
else: | |
parameter = self | |
value = getter(parameter) | |
if get_transformer is None: | |
return value | |
return get_transformer(value) | |
def set(self, value): | |
if not pass_instance_set: | |
parameter = self.user_ID | |
else: | |
parameter = self | |
if set_transformer is None: | |
setter(parameter, value) | |
return | |
setter(parameter, set_transformer(value)) | |
if setter is None: | |
return property(get) | |
return property(get, set) | |
def _set_colour(player, value): | |
tools.set_colour(player.index, value) | |
def _set_location(user_ID, value): | |
es.setpos(user_ID, *value) | |
def _set_model(player, value): | |
colour = tools.get_colour(player.index) | |
es.setplayerprop(player.index, "CBaseEntity.m_nModelIndex", | |
es.precachemodel("models/%s" % value)) | |
tools.set_colour(player.index, colour) | |
def _set_score(user_ID, value): | |
es.server.queuecmd("score set %s %s" %(user_ID, value)) | |
def _set_view_angle(user_ID, value): | |
es.setang(user_ID, *value) | |
def _set_view_coordinates(player, value): | |
x, y, z = (value1 - value2 for value1, value2 | |
in itertools.izip(_get_eye_location(player.user_ID), value)) | |
new_y = math.degrees(math.atan(y / x)) | |
if x < 0: | |
new_y += 180 | |
elif y < 0: | |
new_y += 360 | |
x = 0 - math.degrees(math.atan(z / math.sqrt(pow(y, 2) + pow(x, 2)))) | |
_set_view_angle(player.user_ID, (x, y, _get_view_angle[2])) | |
def _set_weapon(user_ID, value): | |
es.sexec(user_ID, "use %s" % value) | |
def _str_transformer(value): | |
return bool(int(value)) | |
def _transform_immortal(value): | |
if value: | |
return 0 | |
return 512 | |
def _transform_jetpack(value): | |
if value: | |
return 4 | |
return 2 | |
def _transform_no_block(value): | |
if value: | |
return 2 | |
return 0 | |
def _transform_no_clip(value): | |
if value: | |
return 8 | |
return 2 | |
_armor = _PlayerEntity("CCSPlayer.m_ArmorValue") | |
_cash = _PlayerEntity("CCSPlayer.m_iAccount") | |
_defuser = _PlayerEntity("CCSPlayer.m_bHasDefuser") | |
_ducked = _PlayerEntity("CBasePlayer.localdata.m_Local.m_bDucked") | |
_flash_alpha = _PlayerEntity("CCSPlayer.m_flFlashMaxAlpha") | |
_flash_bang = _PlayerEntity("CBasePlayer.localdata.m_iAmmo.012") | |
_flash_duration = _PlayerEntity("CCSPlayer.m_flFlashDuration") | |
_he_grenade = _PlayerEntity("CBasePlayer.localdata.m_iAmmo.011") | |
_health = _PlayerEntity("CBasePlayer.m_iHealth") | |
_helmet = _PlayerEntity("CCSPlayer.m_bHasHelmet") | |
_immortal = _PlayerEntity("CBasePlayer.m_lifeState") | |
_in_rescue_zone = _PlayerEntity("CCSPlayer.m_bInHostageRescueZone") | |
_move_type = _PlayerEntity("CBaseEntity.movetype") | |
_nightvision = _PlayerEntity("CCSPlayer.m_bHasNightVision") | |
_nightvision_on = _PlayerEntity("CCSPlayer.m_bNightVisionOn") | |
_no_block = _PlayerEntity("CBaseEntity.m_CollisionGroup") | |
_smoke_grenade = _PlayerEntity("CBasePlayer.localdata.m_iAmmo.013") | |
_speed = _PlayerEntity("CBasePlayer.localdata.m_flLaggedMovementValue") | |
class Player(object): | |
"""Effectively create data store for information about a specific player. | |
Each attribute can be queried and a dynamic value will be returned. Some | |
attributes are also writable. | |
""" | |
active_weapon = _player_property(_get_weapon, pass_instance_get=True, | |
setter=_set_weapon, set_transformer=str) | |
address = _player_property(_PlayerAttribute("address").get, | |
_address_transformer, True) | |
armor = _player_property(_armor.get, setter=_armor.set, | |
set_transformer=int) | |
bot = _player_property(es.isbot, bool) | |
burning = _player_property(_is_burning, pass_instance_get=True) | |
c4 = _player_property(_has_c4, pass_instance_get=True) | |
cash = _player_property(_cash.get, setter=_cash.set, set_transformer=int) | |
colour = _player_property(_get_colour, pass_instance_get=True, | |
setter=_set_colour, set_transformer=tuple, | |
pass_instance_set=True) | |
connected = _player_property(_is_connected) | |
dead = _player_property(_PlayerEntity("CBasePlayer.pl.deadflag").get, bool) | |
deaths = _player_property(_PlayerAttribute("deaths").get, int, | |
pass_instance_get=True) | |
defuser = _player_property(_defuser.get, _modulus_transformer, | |
setter=_defuser.set, set_transformer=int) | |
defusing = _player_property(_PlayerEntity("CCSPlayer.m_bIsDefusing").get, | |
_modulus_transformer) | |
ducked = _player_property(_ducked.get, _modulus_transformer) | |
flash_alpha = _player_property(_flash_alpha.get, int, _flash_alpha.set, int) | |
flash_bang = _player_property(_flash_bang.get, setter=_flash_bang.set, | |
set_transformer=int) | |
flash_duration = _player_property(_flash_duration.get, | |
setter=_flash_duration.set, | |
set_transformer=int) | |
frozen = _player_property(_move_type.get, _frozen_transformer) | |
handle = _player_property(es.getplayerhandle) | |
he_grenade = _player_property(_he_grenade.get, setter=_he_grenade.set, | |
set_transformer=int) | |
health = _player_property(_health.get, setter=_health.set, | |
set_transformer=int) | |
helmet = _player_property(_helmet.get, _modulus_transformer, | |
setter=_helmet.set, set_transformer=int) | |
immortal = _player_property(_immortal.get, _immortal_transformer, | |
setter=_immortal.set, | |
set_transformer=_transform_immortal) | |
in_bomb_zone = _player_property( | |
_PlayerEntity("CCSPlayer.m_bInBombZone").get, _modulus_transformer) | |
in_buy_zone = _player_property( | |
_PlayerEntity("CCSPlayer.m_bInBuyZone").get, _modulus_transformer) | |
in_rescue_zone = _player_property(_in_rescue_zone.get, | |
_modulus_transformer) | |
index = _player_property(_get_index, pass_instance_get=True) | |
jetpack = _player_property(_move_type.get, _jetpack_transformer, | |
setter=_move_type.set, | |
set_transformer=_transform_jetpack) | |
kills = _player_property(_PlayerAttribute("kills").get, int, True, | |
_set_score, int) | |
knife = _player_property(_get_knife, pass_instance_get=True) | |
language = _player_property(_get_language) | |
location = _player_property(es.getplayerlocation, setter=_set_location, | |
set_transformer=tuple) | |
model = _player_property(_PlayerAttribute("model").get, | |
pass_instance_get=True, setter=_set_model, | |
set_transformer=str) | |
name = _player_property(es.getplayername) | |
nightvision = _player_property(_nightvision.get, _modulus_transformer, | |
setter=_nightvision.set, | |
set_transformer=int) | |
nightvision_on = _player_property(_nightvision_on.get, | |
_modulus_transformer, | |
setter=_nightvision_on.set, | |
set_transformer=int) | |
no_block = _player_property(_no_block.get, _no_block_transformer, | |
setter=_no_block.set, | |
set_transformer=_transform_no_block) | |
no_clip = _player_property(_move_type.get, _no_clip_transformer, | |
setter=_move_type.set, | |
set_transformer=_transform_no_clip) | |
on_ground = _player_property(_PlayerEntity("CBasePlayer.m_fFlags").get, | |
_bit_transformer) | |
packet_loss = _player_property(_PlayerAttribute("packetloss").get, int, | |
True) | |
ping = _player_property(_PlayerAttribute("ping").get, int, True) | |
primary = _player_property(_get_primary, pass_instance_get=True) | |
secondary = _player_property(_get_secondary, pass_instance_get=True) | |
serial_number = _player_property(_PlayerAttribute("serialnumber").get, | |
int, True) | |
smoke_grenade = _player_property(_smoke_grenade.get, | |
setter=_smoke_grenade.set, | |
set_transformer=int) | |
spectator = _player_property(_PlayerAttribute("isobserver").get, | |
_str_transformer, True) | |
speed = _player_property(_speed.get, setter=_speed.set, | |
set_transformer=float) | |
steam_ID = _player_property(es.getplayersteamid) | |
team_ID = _player_property(es.getplayerteam) | |
time_connected = _player_property(_PlayerAttribute("timeconnected").get, | |
float, True) | |
tv = _player_property(_PlayerAttribute("ishltv").get, _str_transformer, | |
True) | |
validated = _player_property(_is_validated) | |
vector = _player_property(_get_vector) | |
view_angle = _player_property(_get_view_angle, setter=_set_view_angle, | |
set_transformer=tuple) | |
view_coordinates = _player_property(_get_view_coordinates, | |
setter=_set_view_coordinates, | |
set_transformer=tuple) | |
view_vector = _player_property(_get_view_vector) | |
def __init__(self, user_ID): | |
"""Instantiate a Player object. | |
Arguments: | |
user_ID - the user ID of the player to store information about. | |
""" | |
if not _is_connected(user_ID): | |
raise PlayersError("user ID %s does not exist on the server." | |
% user_ID) | |
self.user_ID = user_ID | |
self._attributes = None | |
self.auto_update_attributes = True | |
def ban_address(self, time, reason=None): | |
"""Ban and kick the player by their ip. | |
Arguments: | |
time - how long the player should be banned by ip for. | |
reason (Keyword Default: None) - why they should be banned. | |
""" | |
es.server.queuecmd("addip %s %s" %(time, self.address)) | |
self.kick(reason) | |
def ban_steam_ID(self, time, reason=None): | |
"""Ban and kick the player by their steam ID. | |
Arguments: | |
time - how long the player should be banned by steam ID for. | |
reason (Keyword Default: None) - why they should be banned. | |
""" | |
es.server.queuecmd("banid %s %s" %(self.user_ID, time)) | |
self.kick(reason) | |
def burn(self): | |
"""Set the player on fire, causing them to take damage.""" | |
es.server.cmd("es_xfire %s !self ignite" % self.user_ID) | |
def change_team(self, team): | |
"""Move the player to a different team. | |
Arguments: | |
team - the integer of the team to change the player to. Rather than | |
directly passing an integer, players.SPECTATOR, players.TERRORIST | |
and players.COUNTER_TERRORIST should be passed. | |
""" | |
es.server.queuecmd("es_xchangeteam %s %s" %(self.user_ID, team)) | |
def drop(self, weapon_name=None): | |
"""Force the player to drop their weapon. If a specific weapon is | |
passed, cause them to switch to it first. | |
Arguments: | |
weapon_name (Keyword Default: None) - the full name of the weapon to | |
drop. | |
""" | |
weapon_name = weapons.transform_weapon_name(weapon_name) | |
if weapon_name is not None: | |
if weapon_name not in (self.primary.weapon_type.name, | |
self.secondary.weapon_type.name, | |
self.c4.weapon_type.name): | |
raise PlayersError("weapon %s is not owned by player user ID," | |
" %s" %(weapon_name, self.user_ID)) | |
_set_weapon(self.user_ID, weapon_name) | |
es.sexec(self.user_ID, "drop") | |
def extinguish(self): | |
"""Stop the player from being on fire and taking damage from | |
burning. | |
""" | |
for index in _get_flame_indexes(self): | |
es.setindexprop(index, "CEntityFlame.m_flLifetime", 0) | |
def give(self, entity_classname): | |
"""Give the player an entity by it's classname. | |
Arguments: | |
entity_classname - the full keyvalue name of the weapon as it appears | |
in es.createentityindexlist. | |
""" | |
es.delayed(0, "es_xgive %s %s" %(self.user_ID, entity_classname)) | |
def flash(self, alpha, duration): | |
"""Forcefully flash the player a given brightness for a given time. | |
Arguments: | |
alpha - the brightness to flash them. | |
duration - the time in seconds to flash them for including the | |
brightness fade time. | |
""" | |
self.flash_alpha = 0 | |
self.flash_duration = 0 | |
self.flash_alpha = alpha | |
self.flash_duration = duration | |
def freeze(self): | |
"""Freeze the player, stopping them from being able to move. If they | |
were moving before, footstep sounds will continue to emit and weapon | |
fire will resemble any inaccuracies. | |
""" | |
es.setplayerprop(self.user_ID, "CBaseEntity.movetype", 0) | |
def kick(self, reason=None): | |
"""Forcefully remove the player from the server. | |
Arguments: | |
reason (Keyword Default: None) - why the should be kicked. | |
""" | |
if reason is None: | |
es.server.queuecmd("kickid %s" % self.user_ID) | |
else: | |
es.server.queuecmd("kickid %s %s" %(self.user_ID, reason)) | |
def kill(self): | |
"""Force the player to commit suicide and die.""" | |
es.sexec(self.user_ID, "kill") | |
def push(self, horizontal, vertical, vertical_override=False): | |
"""Forcefully move the player in a given direction. | |
Keyword Arguments: | |
horizontal - the horizontal component value to move the player. | |
vertical - the vertical component value to move the player. | |
vertical_override (Keyword Default: False) - whether or not the | |
vertical value should take precedence in calculating the vector to | |
move the player through. | |
""" | |
x, y, z = _get_vector(self.user_ID) | |
horizontal_x = float(horizontal * x) | |
horizontal_y = float(horizontal * y) | |
if not vertical_override: | |
vertical_z = 0 | |
else: | |
vertical_z = vertical | |
es.setplayerprop(self.user_ID, | |
"CBasePlayer.localdata.m_vecBaseVelocity", "%f,%f,%f" | |
%(horizontal_x, horizontal_y, vertical_z)) | |
def strip(self): | |
"""Strip the player of all their weapons.""" | |
strip_entity = "player_weaponstrip" | |
self.give(strip_entity) | |
es.delayed(0, "es_xfire %s %s strip" %(self.user_ID, strip_entity)) | |
def unfreeze(self): | |
"""Unfreeze the player, allowing them to move again.""" | |
es.setplayerprop(self.user_ID, "CBaseEntity.movetype", 2) | |
def update_attributes(self): | |
"""Update the cached attribute values for the player.""" | |
self._attributes = es.createplayerlist(self.user_ID)[self.user_ID] | |
# Create a single public way of obtaining a base object of all players which | |
# can be further filtered. | |
all_players = functools.partial(tools.Filter, (Player(user_ID) for user_ID | |
in es.getUseridList())) | |
def get_player_from_handle(handle): | |
"""Return the user's unique session ID from a handle ID. | |
Arguments: | |
handle - the handle ID to get a user's unique session ID from. | |
""" | |
for player in all_players(): | |
if handle == player.handle: | |
return player | |
return None | |
def get_player_from_name(name): | |
"""Return the user's unique session ID from a name. Full-name matches have | |
a higher order of precendence than partial matches. | |
Arguments: | |
name - the name to get a user's unique session ID from. | |
""" | |
user_data = dict((player.name, player) | |
for player in all_players()) | |
player = user_data.get(name) | |
if player: | |
return player | |
for user_name in user_data: | |
if name in user_name: | |
return user_data[user_name] | |
raise KeyError(name) | |
def get_player_from_steam_ID(steam_ID): | |
"""Return the user's unique session ID from a steam ID. | |
Arguments: | |
steam_ID - the steam ID to get a user's unique session ID from. | |
""" | |
for player in all_players(): | |
es.msg("steam_ID = %s" % player.steam_ID) | |
if steam_ID == player.steam_ID: | |
return player | |
raise KeyError(steam_ID) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment