Best way to hook something on player

Please post any questions about developing your plugin here. Please use the search function before posting!
Hedgehog
Member
Posts: 62
Joined: Sun Nov 03, 2013 8:54 pm

Best way to hook something on player

Postby Hedgehog » Sat Jul 26, 2014 9:54 am

I'm trying to hook several actions like WeaponCanUse, TakeDamage, etc (all from SDKHooks) on a player. And after some time of suffering I got this (for some reason it works...):

Syntax: Select all

from core import PLATFORM
from entities.helpers import index_from_pointer
from events import Event
from filters.players import PlayerIter
from memory import Convention, Argument, Return
from memory.hooks import HookType
from players.helpers import pointer_from_userid

funcs = {}


@Event
def player_activate(event):
userid = event.get_int('userid')

hook_weapon_can_use(userid)


def hook_weapon_can_use(userid):
funcs[userid] = pointer_from_userid(userid).make_virtual_function(
259 if PLATFORM == 'windows' else 260,
Convention.THISCALL,
(Argument.POINTER, Argument.POINTER, Argument.POINTER),
Return.INT
)

funcs[userid].add_hook(HookType.POST, on_weapon_can_use)
print('Hooked: ' + str(userid))


@Event
def player_disconnect(event):
userid = event.get_int('userid')

funcs[userid].remove_hook(HookType.POST, on_weapon_can_use)
print('Unhooked: ' + str(userid))


def load():
for userid in PlayerIter(return_types=['userid']):
hook_weapon_can_use(userid[0])


def unload():
for userid in PlayerIter(return_types=['userid']):
funcs[userid[0]].remove_hook(HookType.POST, on_weapon_can_use)
print('Unhooked: ' + str(userid[0]))


def on_weapon_can_use(first, second):
print('on_weapon_can_use')
print(index_from_pointer(first[0]))
print(index_from_pointer(first[1]))
print(second)


I have a feeling, that it's not the simplest way to do a hook. Am I right? And no signatures, please! :)
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sat Jul 26, 2014 12:15 pm

This is how I would do it. :smile:

Syntax: Select all

# =============================================================================
# >> IMPORTS
# =============================================================================
import memory

from memory.manager import manager
from memory.manager import CustomType
from memory.manager import Argument
from memory.manager import Return

from filters.players import PlayerIter

from players.helpers import index_from_pointer
from players.entity import PlayerEntity

from events import Event
from core import PLATFORM



# =============================================================================
# >> GLOBAL VARIABLES
# =============================================================================
# This variable will store the CCSPlayer::Weapon_CanUse() function.
Weapon_CanUse = None


# =============================================================================
# >> CLASSES
# =============================================================================
class CCSPlayer(CustomType, metaclass=manager):
'''
This is a reconstruction of the CCSPlayer class.

You can add virtual functions, normal functions, attributes and arrays.

You can even extend the class with your own functions, attribute or
properties!
'''

# bool CCSPlayer::Weapon_CanUse(CBaseCombatWeapon*)
# NOTE: I only pass one "Argument.POINTER". "manager.virtual_function"
# will automatically add the "this" pointer argument.
Weapon_CanUse = manager.virtual_function(
259 if PLATFORM == 'windows' else 260,
(Argument.POINTER,),
Return.BOOL
)

@property
def player_entity(self):
'''
Returns a new PlayerEntity object.
'''

return PlayerEntity(index_from_pointer(self))


# =============================================================================
# >> LOAD & UNLOAD
# =============================================================================
def load():
# Try to hook the function
hook_weapon_can_use()

def unload():
# Try to unhook the function
unhook_weapon_can_use()


# =============================================================================
# >> GAME EVENTS
# =============================================================================
@Event
def player_activate(event):
# Try to hook the function
hook_weapon_can_use()


# =============================================================================
# >> FUNCTONS
# =============================================================================
def hook_weapon_can_use():
'''
Attempts to hook CCSPlayer::Weapon_CanUse().

It will only succeed if there is at least one player on the server.
'''

global Weapon_CanUse

# Did we already hooked the function?
if Weapon_CanUse is not None:
# No need to hook it again
return

# Get all player pointers
players = tuple(PlayerIter(return_types='pointer'))

# Is there no player on the server?
if not players:
# We can't hook this function without a player pointer
return

# Get the first player and convert the pointer to a CCSPlayer object
player = memory.make_object(CCSPlayer, players[0])

# Add a post hook callback for that function
player.Weapon_CanUse.add_post_hook(on_weapon_can_use)

# We will save the Function object, so we can unhook it later without the
# need of a player
Weapon_CanUse = player.Weapon_CanUse

def unhook_weapon_can_use():
'''
Attempts to unhook CCSPlayer::Weapon_CanUse(). If it wasn't hooked, it
won't do anything.
'''

# Did we hook the function?
if Weapon_CanUse is not None:
# Unhook it
Weapon_CanUse.remove_post_hook(on_weapon_can_use)


# =============================================================================
# >> CALLBACKS
# =============================================================================
def on_weapon_can_use(args, return_value):
'''
Called after CCSPlayer::Weapon_CanUse() has been called.

You will be able to modfiy the return value here! Of course you can also
modify the arguments.

@param <args>:
A StackData object that stores the arguments of CCSPlayer::Weapon_CanUse().

args[0] is the "this" pointer. It's a player pointer.
args[1] is a CBaseCombatWeapon pointer. It's the weapon that was checked.

@param <return_value>:
Contains the return value that was returned by the original function.

This will be True if the player can use the weapon, False if he can't use
it.
'''

# Convert the pointer to a CCSPlayer object. If you add more functions or
# attributes to the reconstructed type, you can do pretty cool stuff.
player = memory.make_object(CCSPlayer, args[0])

# You can also reconstruct the CBaseCombatWeapon class, but for now I will
# leave it as a simple pointer
weapon_ptr = args[1]

# Print the player's name
print(player.player_entity.name)
You don't need to hook the function for every player. Once is enough. ;)
Hedgehog
Member
Posts: 62
Joined: Sun Nov 03, 2013 8:54 pm

Postby Hedgehog » Sat Jul 26, 2014 1:05 pm

OMG, thanks! But what will happen after player, whose pointer we used, disconnect?

UPD: How I can get what I should pass in second argument of virtual_function()?
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sat Jul 26, 2014 1:43 pm

If the player leaves the server, nothing will happen. You script will still work.

" wrote:UPD: How I can get what I should pass in second argument of virtual_function()?
What exactly do you mean?
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Sat Jul 26, 2014 2:21 pm

And this is awesome! Makes me happy that the days of Eventscripts are over!

This will eventually find it's way into a full-on library, right? ;)
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sat Jul 26, 2014 2:29 pm

Yeah, it's already quite useful :p
Hedgehog
Member
Posts: 62
Joined: Sun Nov 03, 2013 8:54 pm

Postby Hedgehog » Sat Jul 26, 2014 5:56 pm

Ayuto wrote:What exactly do you mean?

I'm talking about 3rd line from this code:

Syntax: Select all

Weapon_CanUse = manager.virtual_function(
259 if PLATFORM == 'windows' else 260,
(Argument.POINTER,),
Return.BOOL
)


You wrote there Argument.POINTER. But what I should write in other cases?
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sat Jul 26, 2014 6:16 pm

This will tell manager.virtual_function() which argument types the function requires.

Here are some examples:
CCSPlayer::Weapon_CanUse(CBaseCombatWeapon*): As you can see the method only takes one pointer. To be more specific a CBaseCombatWeapon pointer. But the pointer type doesn't matter.

SomeClass::Something(CBaseCombatWeapon*, int, bool): In this case the iterable would look like this: (Argument.POINTER, Argument.INT, Argument.BOOL)

SomeClass::Something2(): This would result in an empty iterable.

This link might help you. http://wiki.sourcepython.com/index.php/memory#Argument
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Sun Jul 27, 2014 12:58 am

Another way, without having to wait for a player to join, is to create a bot, use its pointer, and kick it:

Syntax: Select all

from engines.server import EngineServer
from entities.helpers import pointer_from_edict
from filters.players import PlayerIter
from players.bot import BotManager
from players.helpers import userid_from_pointer

create = False

for pointer in PlayerIter(return_types='pointer'):
break

# Was no player found during iteration?
else:
create = True
edict = BotManager.create_bot('test_bot')
pointer = pointer_from_edict(edict)



# Then, after you have the virtual function, kick it
if create:
EngineServer.server_command('kickid {0}\n'.format(userid_from_pointer(pointer)))


Also, there is currently a rewrite of the internal use for BaseEntity, but the following should work with the current version. Add a file to your server at ../addons/source-python/data/source-python/entities/virtuals/cstrike/CCSPlayer.ini and put the following inside that file:

Syntax: Select all

[weapon_canuse]
name = _ZN9CCSPlayer13Weapon_CanUseEP17CBaseCombatWeapon
convention = THISCALL
arguments = POINTER,POINTER
return_type = BOOL


Now, you can use the following:

Syntax: Select all

from engines.server import EngineServer
from entities.helpers import index_from_edict
from filters.players import PlayerIter
from memory.hooks import PostHook
from players.bot import BotManager
from players.entity import PlayerEntity
from players.helpers import userid_from_pointer

create = False

for player in PlayerIter(return_types='player'):
break

# Was no player found during iteration?
else:
create = True
edict = BotManager.create_bot('test_bot')
index = index_from_edict(edict)
player = PlayerEntity(index)


@PostHook(player.weapon_canuse)
def on_weapon_can_use(args, return_value):
pass



# Then, after you have the virtual function, kick it
if create:
EngineServer.server_command('kickid {0}\n'.format(userid_from_pointer(pointer)))


In the new version, once it has been finalized, more virtual functions will be included in the data already. You shouldn't release a script that adds values like this to source-python's data, this is just as an example. The currently implemented virtuals in this manner are CBasePlayer::GiveNamedItem and CBasePlayer::Weapon_Drop for CS:S. No other games are currently supported.

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 60 guests