SayFilters and SayCommands

Discuss API design here.
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

SayFilters and SayCommands

Postby satoon101 » Wed Jan 09, 2013 12:38 am

Eventually, we will have C++ based SayFilters and SayCommands, but for now they have been implemented Python-side.

Both SayFilters and SayCommands will have 3 arguments passed:

  1. index - the index of the player that said something
  2. teamonly - bool value for whether the say_team command was used instead of say
  3. CCommand - the CCommand instance that allows you to get all text

Syntax: Select all

from commands.say import SayFilter
from commands.say.command import SayCommandDictionary

@SayFilter
def say_filter(index, teamonly, CCommand):

# Get the text used
# The entire text is in argument 1, since it is encased in ""
text = CCommand[1]

# Get the first word
word = text.split()[0]

# Was a command used?
if word in SayCommandDictionary:

# Do nothing
return True

# Do not allow the text in chat if not a command
return False

Syntax: Select all

from commands.say import SayCommand
from entities.entity import BaseEntity
from filters.weapontags import WeaponTagIter
from players.entity import PlayerEntity
from weapons.manager import WeaponManager

# Store a list of primary and secondary weapons
_weapons = {
'primary': list(WeaponTagIter('primary', return_types='classname')),
'secondary': list(WeaponTagIter('secondary', return_types='classname')),
}

@SayCommand('!give')
def test_command(index, teamonly, CCommand):

# Get the text
text = CCommand[1].split()

# Was a weapon given?
if not len(text) > 1:

# Do nothing
return True

# Get the weapon
# text[0] is the "!give" command itself
weapon = text[1]

# Was a proper weapon name given?
if not weapon in WeaponManager:

# Do nothing
return True

# Get the player's PlayerEntity instance
player = PlayerEntity(index)

# Get the weapon's proper name
weapon = WeaponManager[weapon].name

# Loop through primary and secondary weapons
for weapon_type in _weapons:

# Is the weapon in the current list?
if not weapon in _weapons[weapon_type]:

# If not, continue to the next weapon type
continue

# Get the player's current weapon of this type
weapon_index = player.get_weapon_index(is_filters=weapon_type)

# Does the player currently have a weapon of this type?
if weapon_index is None:

# If not, no need to remove their current weapon
break

# Get the weapon's BaseEntity instance
current_weapon = BaseEntity (index)

# Is the given weapon the same as the one the player already owns?
if current_weapon.classname == weapon:

# If so, no need to remove their current weapon
break

# Remove the weapon from the player's inventory
player.drop_weapon(current_weapon.pointer, True, True)
current_weapon.remove()

# Give the player the new weapon
player.give_named_item(weapon, 0, True)

If you have any questions, please feel free to ask. If you have any suggestions, please feel free to post them, as well.

Satoon
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Wed Jan 09, 2013 3:38 am

As a note, which I failed to fully illustrate above, since "say" and "say_team" are Server Commands, the Say Commands and Say Filters were both structured so that returning a "False" value will stop the command from fully executing on the server. If you look in the Say Filters example, I return False if the first word is not a command. This would stop "all" chat from showing to players. In the same way, if you do not return False for your Say Commands, the chat messages will still show. You have full control over whether or not the chat messages show for "your" script's commands.

Also note that not returning anything, which returns "None", will act as if you returned a "True" value.

One thing I was questionable on is whether to store if a callback returns False and keep looping through all callbacks for a command and all callbacks for Say Filters, or simply returning the first time a script wants to block it. Currently, I have it set up to block immediately upon receiving its first False return. I would like to hear some opinions on this, so please post your thoughts on that, if you would.

Thank you,
Satoon
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Postby L'In20Cible » Thu Jan 10, 2013 3:59 am

Hey Satoon,

Works like a charm, good job! For the looping thing, I'd say that breaking the loop is the way to go. If the command has been blocked, the other callbacks doesn't really need to get called as the command is not executed in any case. I'm curious if someone can think about a good situation where beeing executed, no matters if the command already been blocked, is "needed" or could be usefull.

L'In20Cible
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Thu Jan 10, 2013 6:50 am

That is what I was thinking as well, which is why I designed it that way, but I would still love to hear a rebuttal on this.

As another note, I just recently added the ability to register multiple Say Commands to one callback (this goes for Server and Client commands as well):

Syntax: Select all

from commands.say import SayCommand
from commands.client import ClientCommand
from commands.server import ServerCommand

# Every argument provided will be considered a command that gets registered
@SayCommand('!test', '@test', '/test', 'test')
def test_say_command(index, teamonly, CCommand):
command_used = CCommand[1].split()[0]
print(command_used)

# As with SayCommand, each argument will be registered as a command
@ClientCommand('this', 'is', 'a', 'test')
def test_client_command(edict, CCommand):
command_used = CCommand[0]
print(command_used)

# Unlike Say and Client Commands, ServerCommand has more than one argument (description and flags)
# For this reason, you must pass the first argument as a tuple or list if you wish to register more than one command
# If you wish to only register one command, you can simply pass the string
@ServerCommand(['server', 'command', 'testing'])
def test_server_command(CCommand):
command_used = CCommand[0]
print(command_used)
Satoon
freddukes
Developer
Posts: 29
Joined: Mon Nov 19, 2012 10:12 pm

Postby freddukes » Thu Jan 10, 2013 8:10 am

L'In20Cible wrote:Hey Satoon,

Works like a charm, good job! For the looping thing, I'd say that breaking the loop is the way to go. If the command has been blocked, the other callbacks doesn't really need to get called as the command is not executed in any case. I'm curious if someone can think about a good situation where beeing executed, no matters if the command already been blocked, is "needed" or could be usefull.

L'In20Cible


I would actually disagree to this. I think that if 1 script blocks the commands, it should be passed to all other filters. With regards to the return value, I suspect if any return True, then it should be blocked.

An example would be something like the command "!help". Multiple scripts may require the use of that say command, but if the first one blocks it then then the other scripts would not receive it. I propose the fillters would look like this:

Syntax: Select all

def _say_commands(CCommand):
'''Called when someone uses either the "say" or "say_team" command'''

# Use try/except in case no arguments are given
try:

# Get the first word in case it is a command
name = CCommand[1].split()[0]

# Was an IndexError encountered
except IndexError:

# If so, simply return True
return True

# Is the first word a command or are there any say filters?
if not (name in SayCommandDictionary or SayFilterList):

# If not, returt True
return True

# Get the index of the player that issued the command
index = Shared.GetCommandIndex()

# Get a bool value for whether the command was team-only
teamonly = CCommand[0] == 'say_team'

return_val = True

# Was a registered command used?
if name in SayCommandDictionary:

# Loop through each callback for the given command
for instance in SayCommandDictionary[name]:

# Call the callback
return_type = instance(index, teamonly, CCommand)

# Does the command now need blocked?
return_val = return_val and (return_type is None or bool(return_type))

# Loop through all say filters
for callback in SayFilterList:

# Call the say filter
return_type = callback(index, teamonly, CCommand)

# Does the command now need blocked?
return_val = return_val and (return_type is None or bool(return_type))

# Allow the text to print in chat
return return_val


That way, all commands get executed, but if any of the say filters / commands return False, then the command will be blocked.

-freddukes
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Postby L'In20Cible » Thu Jan 10, 2013 9:31 am

Hey freddukes,

I would agree with you for say commands as we explicitly ask for them but definitely not for a say filter. If the text has been blocked → there's nothing left to filter. Of course, if there's a way to know the current "status" of the filter that could be different. In example, a fourth parameters passed (holding True if the command has been filtered by another callback and False otherwise) to calbacks so we can all adapt our code depending if the text already been filtered or not.

L'In20Cible
freddukes
Developer
Posts: 29
Joined: Mon Nov 19, 2012 10:12 pm

Postby freddukes » Thu Jan 10, 2013 1:07 pm

L'In20Cible wrote:Hey freddukes,

I would agree with you for say commands as we explicitly ask for them but definitely not for a say filter. If the text has been blocked → there's nothing left to filter. Of course, if there's a way to know the current "status" of the filter that could be different. In example, a fourth parameters passed (holding True if the command has been filtered by another callback and False otherwise) to calbacks so we can all adapt our code depending if the text already been filtered or not.

L'In20Cible


I suppose I agree in the case that a filter should actually filter the text, and that should be the only use for it. I was thinking say filters could be used as say commands, but I suppose that's where the difference lies.

I would agree then that say filters should break on the loop, and say commands should progress through all registered commands.

-freddukes
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Fri Jan 11, 2013 1:16 am

Ok, that sounds good to me. That is an easy fix for the current setup. I will add that as soon as I can.

*Edit: this is now fixed. The current way now runs through all command callbacks and allows say filters to be ran as well. If a say filter blocks the command, no more say filters will be looped through.

Satoon
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Postby L'In20Cible » Fri Jan 11, 2013 8:51 am

Hey Satoon,

As a side note, there should be a try/except when calling callbacks otherwise it will break the loop itself if any errors occurred.

L'In20Cible
User avatar
Ayuto
Project Leader
Posts: 2193
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Fri Jan 11, 2013 7:30 pm

Nice! Any news about these Linux issues with the OB engine?
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Sat Jan 12, 2013 1:21 am

Good point, L'In20Cible. I will add that and the missing import that Omega mentioned here soon.

No, unfortunately we have not made headway with those issues as of yet.

* Edit: added the try/excepts. I am also working on a newer version of the commands module. I realize some scripters will want to register and unregister the commands directly instead of having to have their callbacks auto-unregistered on script unload, so I am adding a registry for each of the command types. I'm not sure when this will be done, but I will post about it when it is.

Satoon
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sat May 16, 2015 11:14 pm

Apparently SayFilters doesn't work for CS:S but works for CS:GO

Syntax: Select all

from commands.say import SayFilter

@SayFilter
def say_test(playerinfo, teamonly, command):
print('Check.')

Never prints anything :(
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Postby L'In20Cible » Sun May 17, 2015 2:59 am

What if you try commands.clients.ClientCommand with say or say_team?
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Sun May 17, 2015 12:05 pm

L'In20Cible wrote:What if you try commands.clients.ClientCommand with say or say_team?


Same thing, no errors but nothing nappens. Works fine in CS:GO
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Sun May 17, 2015 1:10 pm

It works fine for me:

Syntax: Select all

from commands.client import ClientCommandFilter
from commands.say import SayFilter
from commands.server import ServerCommand


@ServerCommand('say')
def server_say_command(command):
print('Server Command Say:', command.get_arg_string())


@SayFilter
def say_filter(playerinfo, teamonly, command):
print('Say Filter:', command.get_arg_string())


@ClientCommandFilter
def client_command_filter(playerinfo, command):
print('Client Command Filter:', command.get_arg_string())


After that is loaded on the server, whenever I say something, it prints both the server and say messages in the console. I just tested this on CS:GO, CS:S, DOD:S, HL2:DM, and TF2 and it worked on all games.
Image
User avatar
Ayuto
Project Leader
Posts: 2193
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sun May 17, 2015 2:40 pm

Do have any other server plugins loaded?
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Mon May 18, 2015 7:58 am

Yeah sorry for the fuss, apparently we had some kind of Source Mod's chat plugin enabled which I wasn't aware of. My bad, everything works fine :P
MrMalina
Member
Posts: 53
Joined: Mon Apr 08, 2013 2:40 pm
Location: Russian Federation

Postby MrMalina » Thu Nov 05, 2015 6:28 pm

And what an analog command SayCommandDictionary in the current sp version?
User avatar
Ayuto
Project Leader
Posts: 2193
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Fri Nov 06, 2015 12:12 pm

Which SayCommandDictionary? We don't have a SayCommandDictionary class. What do you want to do?
MrMalina
Member
Posts: 53
Joined: Mon Apr 08, 2013 2:40 pm
Location: Russian Federation

Postby MrMalina » Fri Nov 06, 2015 7:24 pm

In the first message of this topic is an example:

Syntax: Select all

from commands.say import SayFilter
from commands.say.command import SayCommandDictionary

@SayFilter
def say_filter(index, teamonly, CCommand):

# Get the text used
# The entire text is in argument 1, since it is encased in ""
text = CCommand[1]

# Get the first word
word = text.split()[0]

# Was a command used?
if word in SayCommandDictionary:

# Do nothing
return True

# Do not allow the text in chat if not a command
return False


As I understand it, is a dictionary with all the commands in the chat, in the current sp get a list of these commands?

Return to “API Design”

Who is online

Users browsing this forum: No registered users and 18 guests