by Kirill "iPlayer" Mysnik
GitHub repo: KirillMysnik/SP-CCP
What's this?
This package opens one extra TCP port on your Source Dedicated Server - that will allow plugins to listen for incoming connections and communicate with external applications.
GitHub repo includes 3 directories: examples, srcds and external.
examples folder contains examples / sample scripts (both plugins and external scripts);
srcds folder contains custom package for Source.Python;
external folder contains almost the same package but for external applications (e.g. no AutoUnload usage, GameThread is replaced with Thread etc).
Examples
Examples will be stored at GitHub repo together with the package: https://github.com/KirillMysnik/SP-CCP/tree/master/examples
Let's take a look a the Admin Chat example. It contains Source.Python plugin and external Python 3.5 script. The main purpose of the script is to receive a text input from console and then send this text to the game chat:


Admin Chat (server plugin)
Syntax: Select all
from colors import RED, WHITE
from messages import SayText2
from ccp.receive import RequestBasedReceiver
@RequestBasedReceiver('ccp_admin_chat')
def ccp_admin_chat_receiver(addr, data):
SayText2("{color1}ADMIN: {color2}{message}".format(
color1=RED,
color2=WHITE,
message=data.decode('utf-8')
)).send()
return "OK"
Admin Chat (external script)
Syntax: Select all
#!python3
from ccp.constants import CommunicationMode
from ccp.transmit import SRCDSClient
class TestPluginClient(SRCDSClient):
def on_connected(self):
print("Connection established, setting mode to REQUEST-BASED...")
self.set_mode(CommunicationMode.REQUEST_BASED)
def on_comm_accepted(self):
self.prompt_message()
def on_data_received(self, data):
if data == b"OK":
print("Message was delivered successfully!")
else:
print("Failed to deliver the message")
self.prompt_message()
def on_comm_error(self):
print("Something went wrong on the other side...")
def on_protocol_error(self):
print("There was a misunderstanding between CCP package we use and "
"CCP package SRCDS uses")
def on_comm_end(self):
print("Communication has ended without errors.")
def on_nobody_home(self):
print("Receiving plugin has been unloaded (or was not loaded at all)")
def prompt_message(self):
message = input("Enter the message (leave empty to quit): ")
if not message:
self.stop()
else:
self.send_data(message.encode('utf-8'))
test_plugin_client = TestPluginClient(('127.0.0.1', 28080), 'ccp_admin_chat')
test_plugin_client.start()
Installation
SRCDS
1. Install latest Source.Python
3. Copy contents of the srcds folder from the latest CCP release archive to your mod directory (e.g. "cstrike").
4. Go to addons/source-python/data/custom/ccp/config.ini. It should look something like this:
Code: Select all
[server]
host=
port=28080
whitelist=127.0.0.1,localhost
port option is custom communication port number of the game server;
whitelist option is a comma-separated list of the hosts that are allowed to connect to the custom communication port of the game server.
API
ccp.receive.RequestBasedReceiver
It's a decorator that you use the following way:
Syntax: Select all
@RequestBasedReceiver("my_plugin")
def my_plugin_receiver(addr, data):
...
Using the code above, my_plugin_receiver will be called every time some external application sends something to your plugin using "request-based" communication. Note that it can be called multiple times during one connection or connection may be closed after every call - you won't see the difference. The callback receives two positional arguments: first one is the address of the external application (a pair (host, port) where host is a string representing either a hostname or an IPv4 address, and port is an integer) and the second one is the data the application has sent to your plugin. Type of data is always bytes.
The callback should always return either str or bytes value which then will be sent back to the external application. If callback returns str value, it's encoded into bytes using UTF-8.
If the callback fails to return a proper value or raises an exception, connection to the external app will be terminated.
ccp.receive.RawReceiver
It's a class you should subclass the following way:
Syntax: Select all
class MyPluginReceiver(RawReceiver):
plugin_name = "my_plugin"
def on_data_received(self, data):
...
def on_connection_abort(self):
...
Using RawReceiver is a more flexible approach. An instance of MyPluginReceiver will be automatically created every time some external application establishes a connection and sets its mode to "raw" communication.
With the default constructor ommited, every MyPluginReceiver instance will have an addr attribute and two methods: send_data and stop.
send_data(data) accepts only one positional argument and it should be the data to send to the external application - either bytes or str.
stop() doesn't accept any arguments and just stops the communication with the external application.
The MyPluginReceiver instance may also override two other methods: on_data_received and on_connection_abort.
on_data_received(data) will be called with one positional argument - the data that external application has sent to your plugin. Type of data is always bytes.
on_connection_abort() will be called when connection is unexpectedly closed or aborted.
ccp.transmit.SRCDSClient
It's a class you use to connect to the plugin on the game server. I haven't written a documentation to its API yet, but you can use Admin Chat external script as an example. Also look through these lines: https://github.com/KirillMysnik/SP-CCP/blob/master/srcds/addons/source-python/packages/custom/ccp/transmit.py#L78-L127
Summary
Main reason of writing this package: currently MOTDPlayer package binds its own TCP-port just for its needs. If I make MOTDPlayer depend on CCP, that'll allow to share this extra TCP-port (as you obviously don't want every plugin to open its extra port).
As for now, using this package allows you to:
- establish a connection from one game server to another and exchange data
- establish a connection from an external application to the game server and exchange data
- establish a connection from the game server to an external application
As for now, it's possible to create a thousand connections from an external application to the game server and just abandon them - so be careful what hosts you whitelist.
As for now, RawReceiver class is completely untested.
GitHub repo: KirillMysnik/SP-CCP
Thanks.