<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://springrts-wiki.p2004a.com/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bibim</id>
	<title>Spring - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://springrts-wiki.p2004a.com/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bibim"/>
	<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/wiki/Special:Contributions/Bibim"/>
	<updated>2026-11-08T18:31:27Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.11</generator>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16265</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16265"/>
		<updated>2024-07-15T10:08:22Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Replaced content with &amp;quot;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development-(Python) SPADS plugin development (Python)]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development-(Python) SPADS plugin development (Python)]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16210</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16210"/>
		<updated>2024-07-15T10:04:24Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Replaced content with &amp;quot;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development SPADS plugin development]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development SPADS plugin development]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_install_notes&amp;diff=16270</id>
		<title>SPADS plugin install notes</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_install_notes&amp;diff=16270"/>
		<updated>2024-07-15T10:02:33Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Replaced content with &amp;quot;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-install-notes SPADS plugin install notes]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-install-notes SPADS plugin install notes]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_update_procedure_for_new_Spring_version&amp;diff=16277</id>
		<title>SPADS update procedure for new Spring version</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_update_procedure_for_new_Spring_version&amp;diff=16277"/>
		<updated>2024-07-15T10:01:17Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Replaced content with &amp;quot;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-update-procedure-for-new-Spring-version SPADS update procedure for new Spring version]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-update-procedure-for-new-Spring-version SPADS update procedure for new Spring version]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16173</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16173"/>
		<updated>2024-07-15T09:47:10Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Replaced content with &amp;quot;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-official-plugins-list SPADS official plugins list]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page is now hosted on GitHub: [https://github.com/Yaribz/SPADS/wiki/SPADS-official-plugins-list SPADS official plugins list]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16132</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16132"/>
		<updated>2024-07-15T09:38:48Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Support ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation]&lt;br /&gt;
*[https://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=17130 Discussion in Spring forums]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-official-plugins-list Official plugins list]&lt;br /&gt;
&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-update-procedure-for-new-Spring-version SPADS update procedure for new Spring version]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/UPDATE.md Manual procedure for major SPADS update]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-install-notes Plugin installation notes]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Balancing-teams-on-a-LAN-server Balancing teams on a LAN server]&lt;br /&gt;
&lt;br /&gt;
== Beyond All Reason specific ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Windows) Beyond All Reason LAN server quick install guide (Windows)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Linux) Beyond All Reason LAN server quick install guide (Linux)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-client-configuration-to-connect-to-a-LAN-server Beyond All Reason client configuration to connect to a LAN server]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-customization Beyond All Reason LAN server customization]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-offline-LAN-server-guide Beyond All Reason offline LAN server guide]&lt;br /&gt;
&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development Plugin development tutorials (Perl)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development-(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16131</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16131"/>
		<updated>2024-07-15T09:32:35Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;span style=&amp;quot;font-weight:bold; font-size:large;&amp;quot;&amp;gt;SPADS links&amp;lt;/span&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation]&lt;br /&gt;
*[https://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=17130 Discussion in Spring forums]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-official-plugins-list Official plugins list]&lt;br /&gt;
&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-update-procedure-for-new-Spring-version SPADS update procedure for new Spring version]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/UPDATE.md Manual procedure for major SPADS update]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-install-notes Plugin installation notes]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Balancing-teams-on-a-LAN-server Balancing teams on a LAN server]&lt;br /&gt;
&lt;br /&gt;
== Beyond All Reason specific ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Windows) Beyond All Reason LAN server quick install guide (Windows)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Linux) Beyond All Reason LAN server quick install guide (Linux)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-client-configuration-to-connect-to-a-LAN-server Beyond All Reason client configuration to connect to a LAN server]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-customization Beyond All Reason LAN server customization]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-offline-LAN-server-guide Beyond All Reason offline LAN server guide]&lt;br /&gt;
&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development Plugin development tutorials (Perl)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development-(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16146</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16146"/>
		<updated>2024-03-16T11:19:51Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python. This document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network].&lt;br /&gt;
&lt;br /&gt;
To install the latest stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
To install the latest stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;, enter following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm Inline::Python&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16172</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16172"/>
		<updated>2024-02-26T22:00:39Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists all official SPADS plugins, with basic descriptions (more information is usually available in the README file of the plugin). Refer to [http://springrts.com/wiki/SPADS_plugin_install_notes SPADS plugin install notes] for installation instructions.&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoPauseOnVote.pm AutoPauseOnVote] ==&lt;br /&gt;
automatically pauses the game when a vote is called&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoRegister.zip AutoRegister] ==&lt;br /&gt;
automatically registers the lobby account used by SPADS if needed&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoSpec.zip AutoSpec] ==&lt;br /&gt;
automatically warns/specs unsync/unready players with configurable delays and thresholds&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/BarAutoUpdate.zip BarAutoUpdate] ==&lt;br /&gt;
auto-update and auto-reload functionalities for various Beyond All Reason components&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/BarChobby.zip BarChobby] ==&lt;br /&gt;
lobby protocol extension to allow BAR Chobby to display some SPADS settings in the GUI&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClientStatusEvents.zip ClientStatusEvents] ==&lt;br /&gt;
extends the plugin API with new event-based callbacks on client status changes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClusterManager.zip ClusterManager] ==&lt;br /&gt;
turns a SPADS instance into a manager of clusters of SPADS instances&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomAliases.zip CustomAliases] ==&lt;br /&gt;
makes it possible to define your own aliases for all SPADS commands&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomTeamColors.zip CustomTeamColors] ==&lt;br /&gt;
makes it possible to customize the team colors applied by SPADS&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DownloadArchives.zip DownloadArchives] ==&lt;br /&gt;
adds !dlmap and !dlmod commands for map and mod downloads&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DynamicMapRotation.zip DynamicMapRotation] ==&lt;br /&gt;
changes rotation map list automatically according to battle context&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/FirstWelcomeMsg.zip FirstWelcomeMsg] ==&lt;br /&gt;
sends a message to a player the first time he joins the battle only&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/InGameMute.zip InGameMute] ==&lt;br /&gt;
adds !mute, !unmute and !mutes commands for in-game mutes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/LanServer.zip LanServer] ==&lt;br /&gt;
implements a lobby server running in LAN mode (allows SPADS to operate autonomously)&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/PreventVoteExploit.zip PreventVoteExploit] ==&lt;br /&gt;
tries to prevent vote exploits using multi accounts&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RecommendedMap.zip RecommendedMap] ==&lt;br /&gt;
chooses map automatically depending on game type and number of players&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RequiredChannel.zip RequiredChannel] ==&lt;br /&gt;
forces players to be in a channel before being able to join the battle&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/Resign.zip Resign] ==&lt;br /&gt;
adds !resign command and specific vote system to resign as a team&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ReverseLookup.zip ReverseLookup] ==&lt;br /&gt;
shows hostnames near IP addresses in !status output&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/SpringieExtension.zip SpringieExtension] ==&lt;br /&gt;
implements Springie/Zero-K lobby protocol extension&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ZeroK.zip ZeroK] ==&lt;br /&gt;
implements Zero-K specific functionalities&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16171</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16171"/>
		<updated>2024-02-26T21:43:50Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists all official SPADS plugins, with basic descriptions (more information is usually available in the README file of the plugin). Refer to [http://springrts.com/wiki/SPADS_plugin_install_notes SPADS plugin install notes] for installation instructions.&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoPauseOnVote.pm AutoPauseOnVote] ==&lt;br /&gt;
automatically pauses the game when a vote is called&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoRegister.zip AutoRegister] ==&lt;br /&gt;
automatically registers the lobby account used by SPADS if needed&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoSpec.zip AutoSpec] ==&lt;br /&gt;
automatically warns/specs unsync/unready players with configurable delays and thresholds&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/BarAutoUpdate.zip BarAutoUpdate] ==&lt;br /&gt;
auto-update and auto-reload functionalities for various Beyond All Reason components&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/BarChobby.zip BarChobby] ==&lt;br /&gt;
lobby protocol extension to allow BAR Chobby to display some SPADS settings in the GUI&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClientStatusEvents.zip ClientStatusEvents] ==&lt;br /&gt;
extends the plugin API with new event-based callbacks on client status changes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClusterManager.zip ClusterManager] ==&lt;br /&gt;
turns a SPADS instance into a manager of clusters of SPADS instances&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomAliases.zip CustomAliases] ==&lt;br /&gt;
makes it possible to define your own aliases for all SPADS commands&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomTeamColors.zip CustomTeamColors] ==&lt;br /&gt;
makes it possible to customize the team colors applied by SPADS&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DownloadArchives.zip DownloadArchives] ==&lt;br /&gt;
adds !dlmap and !dlmod commands for map and mod downloads&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DynamicMapRotation.zip DynamicMapRotation] ==&lt;br /&gt;
changes rotation map list automatically according to battle context&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/FirstWelcomeMsg.zip FirstWelcomeMsg] ==&lt;br /&gt;
sends a message to a player the first time he joins the battle only&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/InGameMute.zip InGameMute] ==&lt;br /&gt;
adds !mute, !unmute and !mutes commands for in-game mutes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/PreventVoteExploit.zip PreventVoteExploit] ==&lt;br /&gt;
tries to prevent vote exploits using multi accounts&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RecommendedMap.zip RecommendedMap] ==&lt;br /&gt;
chooses map automatically depending on game type and number of players&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RequiredChannel.zip RequiredChannel] ==&lt;br /&gt;
forces players to be in a channel before being able to join the battle&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/Resign.zip Resign] ==&lt;br /&gt;
adds !resign command and specific vote system to resign as a team&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ReverseLookup.zip ReverseLookup] ==&lt;br /&gt;
shows hostnames near IP addresses in !status output&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/SpringieExtension.zip SpringieExtension] ==&lt;br /&gt;
implements Springie/Zero-K lobby protocol extension&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ZeroK.zip ZeroK] ==&lt;br /&gt;
implements Zero-K specific functionalities&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16170</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16170"/>
		<updated>2024-02-26T21:28:03Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists all official SPADS plugins, with basic descriptions (more information is usually available in the README file of the plugin). Refer to [http://springrts.com/wiki/SPADS_plugin_install_notes SPADS plugin install notes] for installation instructions.&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoPauseOnVote.pm AutoPauseOnVote] ==&lt;br /&gt;
automatically pauses the game when a vote is called&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoRegister.zip AutoRegister] ==&lt;br /&gt;
automatically registers the lobby account used by SPADS if needed&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoSpec.zip AutoSpec] ==&lt;br /&gt;
automatically warns/specs unsync/unready players with configurable delays and thresholds&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/BarChobby.zip BarChobby] ==&lt;br /&gt;
lobby protocol extension to allow BAR Chobby to display some SPADS settings in the GUI&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClientStatusEvents.zip ClientStatusEvents] ==&lt;br /&gt;
extends the plugin API with new event-based callbacks on client status changes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClusterManager.zip ClusterManager] ==&lt;br /&gt;
turns a SPADS instance into a manager of clusters of SPADS instances&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomAliases.zip CustomAliases] ==&lt;br /&gt;
makes it possible to define your own aliases for all SPADS commands&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomTeamColors.zip CustomTeamColors] ==&lt;br /&gt;
makes it possible to customize the team colors applied by SPADS&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DownloadArchives.zip DownloadArchives] ==&lt;br /&gt;
adds !dlmap and !dlmod commands for map and mod downloads&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DynamicMapRotation.zip DynamicMapRotation] ==&lt;br /&gt;
changes rotation map list automatically according to battle context&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/FirstWelcomeMsg.zip FirstWelcomeMsg] ==&lt;br /&gt;
sends a message to a player the first time he joins the battle only&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/InGameMute.zip InGameMute] ==&lt;br /&gt;
adds !mute, !unmute and !mutes commands for in-game mutes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/PreventVoteExploit.zip PreventVoteExploit] ==&lt;br /&gt;
tries to prevent vote exploits using multi accounts&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RecommendedMap.zip RecommendedMap] ==&lt;br /&gt;
chooses map automatically depending on game type and number of players&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RequiredChannel.zip RequiredChannel] ==&lt;br /&gt;
forces players to be in a channel before being able to join the battle&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/Resign.zip Resign] ==&lt;br /&gt;
adds !resign command and specific vote system to resign as a team&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ReverseLookup.zip ReverseLookup] ==&lt;br /&gt;
shows hostnames near IP addresses in !status output&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/SpringieExtension.zip SpringieExtension] ==&lt;br /&gt;
implements Springie/Zero-K lobby protocol extension&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ZeroK.zip ZeroK] ==&lt;br /&gt;
implements Zero-K specific functionalities&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16130</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16130"/>
		<updated>2023-06-14T08:02:15Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Memos */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;span style=&amp;quot;font-weight:bold; font-size:large;&amp;quot;&amp;gt;SPADS links&amp;lt;/span&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation]&lt;br /&gt;
*[https://springrts.com/phpbb/viewforum.php?f=88 Discussion in Spring forums]&lt;br /&gt;
*[http://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_official_plugins_list Official plugins list]&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_update_procedure_for_new_Spring_version SPADS update procedure for new Spring version]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/UPDATE.md Manual procedure for major SPADS update]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_install_notes Plugin installation notes]&lt;br /&gt;
&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development Plugin development tutorials (Perl)]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development_(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16129</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16129"/>
		<updated>2023-06-14T07:58:14Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;span style=&amp;quot;font-weight:bold; font-size:large;&amp;quot;&amp;gt;SPADS links&amp;lt;/span&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation]&lt;br /&gt;
*[https://springrts.com/phpbb/viewforum.php?f=88 Discussion in Spring forums]&lt;br /&gt;
*[http://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_official_plugins_list Official plugins list]&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_update_procedure_for_new_Spring_version SPADS update procedure for new Spring version]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/UPDATE.md SPADS major update documentation]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_install_notes Plugin installation notes]&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development Plugin development tutorials (Perl)]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development_(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_PerlUnitSync_module_generation_for_Windows&amp;diff=16151</id>
		<title>SPADS PerlUnitSync module generation for Windows</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_PerlUnitSync_module_generation_for_Windows&amp;diff=16151"/>
		<updated>2023-06-14T07:57:35Z</updated>

		<summary type="html">&lt;p&gt;Bibim: remove obsolete information&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS:Installation&amp;diff=16139</id>
		<title>SPADS:Installation</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS:Installation&amp;diff=16139"/>
		<updated>2023-06-14T07:51:59Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Remove obsolete information&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16169</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16169"/>
		<updated>2021-10-25T21:54:19Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists all official SPADS plugins, with basic descriptions (more information is usually available in the README file of the plugin). Refer to [http://springrts.com/wiki/SPADS_plugin_install_notes SPADS plugin install notes] for installation instructions.&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoPauseOnVote.pm AutoPauseOnVote] ==&lt;br /&gt;
automatically pauses the game when a vote is called&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoRegister.zip AutoRegister] ==&lt;br /&gt;
automatically registers the lobby account used by SPADS if needed&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoSpec.zip AutoSpec] ==&lt;br /&gt;
automatically warns/specs unsync/unready players with configurable delays and thresholds&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClientStatusEvents.zip ClientStatusEvents] ==&lt;br /&gt;
extends the plugin API with new event-based callbacks on client status changes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClusterManager.zip ClusterManager] ==&lt;br /&gt;
turns a SPADS instance into a manager of clusters of SPADS instances&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomAliases.zip CustomAliases] ==&lt;br /&gt;
makes it possible to define your own aliases for all SPADS commands&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomTeamColors.zip CustomTeamColors] ==&lt;br /&gt;
makes it possible to customize the team colors applied by SPADS&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DownloadArchives.zip DownloadArchives] ==&lt;br /&gt;
adds !dlmap and !dlmod commands for map and mod downloads&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DynamicMapRotation.zip DynamicMapRotation] ==&lt;br /&gt;
changes rotation map list automatically according to battle context&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/FirstWelcomeMsg.zip FirstWelcomeMsg] ==&lt;br /&gt;
sends a message to a player the first time he joins the battle only&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/InGameMute.zip InGameMute] ==&lt;br /&gt;
adds !mute, !unmute and !mutes commands for in-game mutes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/PreventVoteExploit.zip PreventVoteExploit] ==&lt;br /&gt;
tries to prevent vote exploits using multi accounts&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RecommendedMap.zip RecommendedMap] ==&lt;br /&gt;
chooses map automatically depending on game type and number of players&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RequiredChannel.zip RequiredChannel] ==&lt;br /&gt;
forces players to be in a channel before being able to join the battle&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/Resign.zip Resign] ==&lt;br /&gt;
adds !resign command and specific vote system to resign as a team&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ReverseLookup.zip ReverseLookup] ==&lt;br /&gt;
shows hostnames near IP addresses in !status output&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/SpringieExtension.zip SpringieExtension] ==&lt;br /&gt;
implements Springie/Zero-K lobby protocol extension&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ZeroK.zip ZeroK] ==&lt;br /&gt;
implements Zero-K specific functionalities&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16145</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16145"/>
		<updated>2021-10-05T07:07:13Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Method 1: Using cpanminus to install latest version from GitHub */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
In order to be able to install Perl package directly from Git repositories, Git must also be installed on the system. This can be done using following command for example on Debian based distributions: &amp;lt;code&amp;gt;apt-get install git&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, building the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; module requires having Python headers available on the system, as well as the &amp;lt;code&amp;gt;distutils&amp;lt;/code&amp;gt; Python package. You can install them using following command for example on Debian based distributions: &amp;lt;code&amp;gt;apt-get install python3-dev python3-distutils&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; and the dependencies are installed, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 1: Installing cpanminus ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight module manager for Perl, which is able to install Perl modules directly from Git repositories and allows interactive mode installations.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use the generic default Perl module manager &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 2: Using cpanminus to install Inline::Python ==&lt;br /&gt;
As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so it is recommended to install the latest version instead. Unfortunately, the installation script included in the latest version on the module maintainer repository has some problems when installing on Windows. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If for some reason the installation of the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; fails, you can try to install the stable release instead. Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python installation detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; must be run in interactive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16168</id>
		<title>SPADS official plugins list</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_official_plugins_list&amp;diff=16168"/>
		<updated>2021-03-30T20:23:46Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page lists all official SPADS plugins, with basic descriptions (more information is usually available in the README file of the plugin). Refer to [http://springrts.com/wiki/SPADS_plugin_install_notes SPADS plugin install notes] for installation instructions.&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoPauseOnVote.pm AutoPauseOnVote] ==&lt;br /&gt;
automatically pauses the game when a vote is called&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoRegister.zip AutoRegister] ==&lt;br /&gt;
automatically registers the lobby account used by SPADS if needed&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/AutoSpec.zip AutoSpec] ==&lt;br /&gt;
automatically warns/specs unsync/unready players with configurable delays and thresholds&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ClusterManager.zip ClusterManager] ==&lt;br /&gt;
turns a SPADS instance into a manager of clusters of SPADS instances&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomAliases.zip CustomAliases] ==&lt;br /&gt;
makes it possible to define your own aliases for all SPADS commands&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/CustomTeamColors.zip CustomTeamColors] ==&lt;br /&gt;
makes it possible to customize the team colors applied by SPADS&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DownloadArchives.zip DownloadArchives] ==&lt;br /&gt;
adds !dlmap and !dlmod commands for map and mod downloads&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/DynamicMapRotation.zip DynamicMapRotation] ==&lt;br /&gt;
changes rotation map list automatically according to battle context&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/FirstWelcomeMsg.zip FirstWelcomeMsg] ==&lt;br /&gt;
sends a message to a player the first time he joins the battle only&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/InGameMute.zip InGameMute] ==&lt;br /&gt;
adds !mute, !unmute and !mutes commands for in-game mutes&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/PreventVoteExploit.zip PreventVoteExploit] ==&lt;br /&gt;
tries to prevent vote exploits using multi accounts&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RecommendedMap.zip RecommendedMap] ==&lt;br /&gt;
chooses map automatically depending on game type and number of players&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/RequiredChannel.zip RequiredChannel] ==&lt;br /&gt;
forces players to be in a channel before being able to join the battle&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/Resign.zip Resign] ==&lt;br /&gt;
adds !resign command and specific vote system to resign as a team&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ReverseLookup.zip ReverseLookup] ==&lt;br /&gt;
shows hostnames near IP addresses in !status output&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/SpringieExtension.zip SpringieExtension] ==&lt;br /&gt;
implements Springie/Zero-K lobby protocol extension&lt;br /&gt;
&lt;br /&gt;
== [http://planetspads.free.fr/spads/plugins/ZeroK.zip ZeroK] ==&lt;br /&gt;
implements Zero-K specific functionalities&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16264</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16264"/>
		<updated>2021-01-28T14:24:25Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We declare our new command and the associated handler&lt;br /&gt;
        spads.addSpadsCommandHandler({&#039;time&#039;: hSpadsTime})&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We remove our new command handler&lt;br /&gt;
        spads.removeSpadsCommandHandler([&#039;time&#039;])&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler for our new command&lt;br /&gt;
def hSpadsTime(source,user,params,checkOnly):&lt;br /&gt;
&lt;br /&gt;
    # checkOnly is true if this is just a check for callVote command, not a real command execution&lt;br /&gt;
    if checkOnly :&lt;br /&gt;
        &lt;br /&gt;
        # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    # We get current time using &amp;quot;now&amp;quot; function of datetime class from datetime module&lt;br /&gt;
    current_time = datetime.datetime.now()&lt;br /&gt;
    current_time_string = current_time.strftime(&amp;quot;%H:%M:%S&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    # We call the API function &amp;quot;answer&amp;quot; to send back the response to the user who called the command&lt;br /&gt;
    # using same canal as he used (private message, battle lobby, in game message...)&lt;br /&gt;
    spads.answer(&amp;quot;Current local time: %s&amp;quot; % current_time_string)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are retrieving current time in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;datetime&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the datetime module so we can get current time for our !time command&lt;br /&gt;
import datetime&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16263</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16263"/>
		<updated>2021-01-28T14:22:39Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* New-command plugin tutorial (TimePlugin) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We declare our new command and the associated handler&lt;br /&gt;
        spads.addSpadsCommandHandler({&#039;time&#039;: hSpadsTime})&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We remove our new command handler&lt;br /&gt;
        spads.removeSpadsCommandHandler([&#039;time&#039;])&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler for our new command&lt;br /&gt;
def hSpadsTime(source,user,params,checkOnly):&lt;br /&gt;
&lt;br /&gt;
    # checkOnly is true if this is just a check for callVote command, not a real command execution&lt;br /&gt;
    if checkOnly :&lt;br /&gt;
        &lt;br /&gt;
        # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
    # We get current time using &amp;quot;now&amp;quot; function of datetime class from datetime module&lt;br /&gt;
    current_time = datetime.datetime.now()&lt;br /&gt;
    current_time_string = current_time.strftime(&amp;quot;%H:%M:%S&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    # We call the API function &amp;quot;answer&amp;quot; to send back the response to the user who called the command&lt;br /&gt;
    # using same canal as he used (private message, battle lobby, in game message...)&lt;br /&gt;
    spads.answer(&amp;quot;Current local time: %s&amp;quot; % current_time_string)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16262</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16262"/>
		<updated>2021-01-28T13:24:02Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We declare our new command and the associated handler&lt;br /&gt;
        spads.addSpadsCommandHandler({&#039;time&#039;: hSpadsTime})&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
        # We remove our new command handler&lt;br /&gt;
        spads.removeSpadsCommandHandler([&#039;time&#039;])&lt;br /&gt;
        [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler for our new command&lt;br /&gt;
def hSpadsTime(source,user,params,checkOnly):&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16261</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16261"/>
		<updated>2021-01-28T13:20:50Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16209</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16209"/>
		<updated>2021-01-28T13:19:37Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
sub onPrivateMsg {&lt;br /&gt;
  &lt;br /&gt;
  # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
  # $userName is the name of the user sending the private message&lt;br /&gt;
  # $message is the message sent by the user&lt;br /&gt;
  my ($self,$userName,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
  if($message eq &#039;Hello&#039;) {&lt;br /&gt;
    &lt;br /&gt;
    # We send our wonderful Hello World message&lt;br /&gt;
    sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
  # for other SPADS processing&lt;br /&gt;
  return 0;&lt;br /&gt;
  &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
 &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
 &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  &lt;br /&gt;
  # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
  # We return the instantiated plugin&lt;br /&gt;
  return $self;&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
sub onLobbyConnected {&lt;br /&gt;
  &lt;br /&gt;
  # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
  # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
  # handler here.&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called when the plugin is unloaded&lt;br /&gt;
sub onUnload {&lt;br /&gt;
  &lt;br /&gt;
  # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
  removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
sub hLobbySaidBattle {&lt;br /&gt;
  &lt;br /&gt;
  # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
  # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
  # $message is the message said in the battle lobby&lt;br /&gt;
  my ($command,$user,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
  my $p_spadsConf=getSpadsConf();&lt;br /&gt;
  return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
  &lt;br /&gt;
  # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
  # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
  my $p_conf=getPluginConf();&lt;br /&gt;
  return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
  &lt;br /&gt;
  # We put the forbidden words in a array&lt;br /&gt;
  my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
  &lt;br /&gt;
  # We test each forbidden word&lt;br /&gt;
  foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
    &lt;br /&gt;
    # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
    if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
      &lt;br /&gt;
      # Then we kick the user from the battle lobby&lt;br /&gt;
      sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
      queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
      &lt;br /&gt;
      # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
      last;&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler for our new command&lt;br /&gt;
sub hSpadsTime {&lt;br /&gt;
  my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
  return 1 if($checkOnly);&lt;br /&gt;
  &lt;br /&gt;
  my @time = localtime();&lt;br /&gt;
  @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
  &lt;br /&gt;
  answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16208</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16208"/>
		<updated>2021-01-28T13:18:51Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
sub onPrivateMsg {&lt;br /&gt;
  &lt;br /&gt;
  # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
  # $userName is the name of the user sending the private message&lt;br /&gt;
  # $message is the message sent by the user&lt;br /&gt;
  my ($self,$userName,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
  if($message eq &#039;Hello&#039;) {&lt;br /&gt;
    &lt;br /&gt;
    # We send our wonderful Hello World message&lt;br /&gt;
    sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
  # for other SPADS processing&lt;br /&gt;
  return 0;&lt;br /&gt;
  &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
 &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
 &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  &lt;br /&gt;
  # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
  # We return the instantiated plugin&lt;br /&gt;
  return $self;&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
sub onLobbyConnected {&lt;br /&gt;
  &lt;br /&gt;
  # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
  # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
  # handler here.&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called when the plugin is unloaded&lt;br /&gt;
sub onUnload {&lt;br /&gt;
  &lt;br /&gt;
  # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
  removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
sub hLobbySaidBattle {&lt;br /&gt;
  &lt;br /&gt;
  # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
  # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
  # $message is the message said in the battle lobby&lt;br /&gt;
  my ($command,$user,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
  my $p_spadsConf=getSpadsConf();&lt;br /&gt;
  return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
  &lt;br /&gt;
  # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
  # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
  my $p_conf=getPluginConf();&lt;br /&gt;
  return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
  &lt;br /&gt;
  # We put the forbidden words in a array&lt;br /&gt;
  my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
  &lt;br /&gt;
  # We test each forbidden word&lt;br /&gt;
  foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
    &lt;br /&gt;
    # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
    if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
      &lt;br /&gt;
      # Then we kick the user from the battle lobby&lt;br /&gt;
      sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
      queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
      &lt;br /&gt;
      # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
      last;&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # Anyone can call our command from anywhere&lt;br /&gt;
 [time]&lt;br /&gt;
 ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 [time]&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16207</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16207"/>
		<updated>2021-01-28T13:17:14Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
sub onPrivateMsg {&lt;br /&gt;
  &lt;br /&gt;
  # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
  # $userName is the name of the user sending the private message&lt;br /&gt;
  # $message is the message sent by the user&lt;br /&gt;
  my ($self,$userName,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
  if($message eq &#039;Hello&#039;) {&lt;br /&gt;
    &lt;br /&gt;
    # We send our wonderful Hello World message&lt;br /&gt;
    sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
  # for other SPADS processing&lt;br /&gt;
  return 0;&lt;br /&gt;
  &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
 &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
 &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  &lt;br /&gt;
  # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
  # We return the instantiated plugin&lt;br /&gt;
  return $self;&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
sub onLobbyConnected {&lt;br /&gt;
  &lt;br /&gt;
  # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
  # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
  # handler here.&lt;br /&gt;
  addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This callback is called when the plugin is unloaded&lt;br /&gt;
sub onUnload {&lt;br /&gt;
  &lt;br /&gt;
  # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
  removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
sub hLobbySaidBattle {&lt;br /&gt;
  &lt;br /&gt;
  # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
  # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
  # $message is the message said in the battle lobby&lt;br /&gt;
  my ($command,$user,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
  my $p_spadsConf=getSpadsConf();&lt;br /&gt;
  return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
  &lt;br /&gt;
  # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
  # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
  my $p_conf=getPluginConf();&lt;br /&gt;
  return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
  &lt;br /&gt;
  # We put the forbidden words in a array&lt;br /&gt;
  my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
  &lt;br /&gt;
  # We test each forbidden word&lt;br /&gt;
  foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
    &lt;br /&gt;
    # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
    if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
      &lt;br /&gt;
      # Then we kick the user from the battle lobby&lt;br /&gt;
      sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
      queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
      &lt;br /&gt;
      # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
      last;&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16206</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16206"/>
		<updated>2021-01-28T13:15:31Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
sub onPrivateMsg {&lt;br /&gt;
  &lt;br /&gt;
  # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
  # $userName is the name of the user sending the private message&lt;br /&gt;
  # $message is the message sent by the user&lt;br /&gt;
  my ($self,$userName,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
  if($message eq &#039;Hello&#039;) {&lt;br /&gt;
    &lt;br /&gt;
    # We send our wonderful Hello World message&lt;br /&gt;
    sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
  # for other SPADS processing&lt;br /&gt;
  return 0;&lt;br /&gt;
  &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
 &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
 &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16205</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16205"/>
		<updated>2021-01-28T13:15:00Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
sub onPrivateMsg {&lt;br /&gt;
  &lt;br /&gt;
  # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
  # $userName is the name of the user sending the private message&lt;br /&gt;
  # $message is the message sent by the user&lt;br /&gt;
  my ($self,$userName,$message)=@_;&lt;br /&gt;
  &lt;br /&gt;
  # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
  if($message eq &#039;Hello&#039;) {&lt;br /&gt;
    &lt;br /&gt;
    # We send our wonderful Hello World message&lt;br /&gt;
    sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
  # for other SPADS processing&lt;br /&gt;
  return 0;&lt;br /&gt;
  &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16260</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16260"/>
		<updated>2021-01-28T13:13:01Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16259</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16259"/>
		<updated>2021-01-28T13:10:44Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
        [...]&lt;br /&gt;
    &lt;br /&gt;
        # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
    def onLobbyConnected(self,lobbyInterface):&lt;br /&gt;
        &lt;br /&gt;
        # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
        # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
        # handler here.&lt;br /&gt;
        spads.addLobbyCommandHandler({&#039;SAIDBATTLE&#039;: hLobbySaidBattle})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This callback is called when the plugin is unloaded&lt;br /&gt;
    def onUnload(self,reason):&lt;br /&gt;
        &lt;br /&gt;
        # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
        spads.removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
# It is called each time a player says something in the battle lobby.&lt;br /&gt;
#   command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
#   user is the name of the user who said something in the battle lobby&lt;br /&gt;
#   message is the message said in the battle lobby&lt;br /&gt;
def hLobbySaidBattle(command,user,message):&lt;br /&gt;
    &lt;br /&gt;
    # First we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
    # the Inline::Python module transmits them as byte strings&lt;br /&gt;
    (user,message)=spads.fix_string(user,message)&lt;br /&gt;
    &lt;br /&gt;
    # Here we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    spadsConf = spads.getSpadsConf()&lt;br /&gt;
    if user == spadsConf[&#039;lobbyLogin&#039;]:&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    pluginConf = spads.getPluginConf()&lt;br /&gt;
    if int(spads.getUserAccessLevel(user)) &amp;gt;= int(pluginConf[&#039;immuneLevel&#039;]):&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a list&lt;br /&gt;
    forbiddenWords = pluginConf[&#039;words&#039;].split(&#039;;&#039;)&lt;br /&gt;
            &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    for forbiddenWord in forbiddenWords:&lt;br /&gt;
        &lt;br /&gt;
        # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
        if re.search(r&#039;\b&#039; + re.escape(forbiddenWord) + r&#039;\b&#039;,message,re.IGNORECASE):&lt;br /&gt;
            &lt;br /&gt;
            # Then we kick the user from the battle lobby&lt;br /&gt;
            spads.sayBattle(&amp;quot;Kicking %s from battle (watch your language!)&amp;quot; % user)&lt;br /&gt;
            spads.queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,user])&lt;br /&gt;
                    &lt;br /&gt;
            # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
            break&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must not forget to import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16258</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16258"/>
		<updated>2021-01-28T12:35:45Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
 # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
 words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
 # We must define our preset setting in the default preset at least&lt;br /&gt;
 [default]&lt;br /&gt;
  &lt;br /&gt;
 # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
 # or by loading a preset.&lt;br /&gt;
 # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
 immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16257</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16257"/>
		<updated>2021-01-28T11:33:09Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As we are using regular expressions in our plugin code, we must import the &amp;lt;code&amp;gt;re&amp;lt;/code&amp;gt; Python module at the start of our plugin code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Import the regular expression module to check for forbidden words&lt;br /&gt;
import re&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16256</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16256"/>
		<updated>2021-01-28T11:14:08Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
globalPluginParams = { &#039;words&#039;: [] }&lt;br /&gt;
presetPluginParams = { &#039;immuneLevel&#039;: [&#039;integer&#039;,&#039;integerRange&#039;] }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16255</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16255"/>
		<updated>2021-01-28T11:03:19Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Preparing the template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
# &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
# &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
# (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
# setting types)&lt;br /&gt;
my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16254</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16254"/>
		<updated>2021-01-28T10:53:00Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* New-command plugin tutorial (TimePlugin) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/mynewcommandplugin.py the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;timeplugin.py&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/timeplugin.py this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16204</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16204"/>
		<updated>2021-01-28T10:49:12Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* New-command plugin tutorial (TimePlugin) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  sub onPrivateMsg {&lt;br /&gt;
    &lt;br /&gt;
    # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    # $userName is the name of the user sending the private message&lt;br /&gt;
    # $message is the message sent by the user&lt;br /&gt;
    my ($self,$userName,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
    if($message eq &#039;Hello&#039;) {&lt;br /&gt;
      &lt;br /&gt;
      # We send our wonderful Hello World message&lt;br /&gt;
      sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
    # for other SPADS processing&lt;br /&gt;
    return 0;&lt;br /&gt;
    &lt;br /&gt;
  }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; --&amp;gt; &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt;. Then we edit the plugin template &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; and replace &amp;lt;code&amp;gt;MyNewCommandPlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;TimePlugin&amp;lt;/code&amp;gt; in the source code, and we do the same for the plugin configuration template &amp;lt;code&amp;gt;TimePlugin.conf&amp;lt;/code&amp;gt;, so that the &amp;lt;code&amp;gt;commandsFile&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;helpFile&amp;lt;/code&amp;gt; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: &amp;lt;code&amp;gt;TimePluginCmd.conf&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;commands.conf&amp;lt;/code&amp;gt; file. The template provides a default command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; with no requirement. We will just rename this command to &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file &amp;lt;code&amp;gt;TimePluginHelp.dat&amp;lt;/code&amp;gt;. This file uses the same syntax as the standard SPADS &amp;lt;code&amp;gt;help.dat&amp;lt;/code&amp;gt; file, and the template provides a help example for a command &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;lt;code&amp;gt;myCommand&amp;lt;/code&amp;gt;, so all we have to do is to edit our plugin file ( &amp;lt;code&amp;gt;TimePlugin.pm&amp;lt;/code&amp;gt; ) and replace this command by our own &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;lt;code&amp;gt;hMyCommand&amp;lt;/code&amp;gt; by our own handler &amp;lt;code&amp;gt;hSpadsTime&amp;lt;/code&amp;gt;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin TimePlugin load&amp;lt;/code&amp;gt;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our &amp;lt;code&amp;gt;!time&amp;lt;/code&amp;gt; command help should also appear in &amp;lt;code&amp;gt;!help&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;!help time&amp;lt;/code&amp;gt; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16253</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16253"/>
		<updated>2021-01-28T10:03:02Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Configurable plugin tutorial (ForbiddenWords) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/myconfigurableplugin.py the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;myconfigurableplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;forbiddenwords.py&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/forbiddenwords.py this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16203</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16203"/>
		<updated>2021-01-28T09:57:16Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Configurable plugin tutorial (ForbiddenWords) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  sub onPrivateMsg {&lt;br /&gt;
    &lt;br /&gt;
    # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    # $userName is the name of the user sending the private message&lt;br /&gt;
    # $message is the message sent by the user&lt;br /&gt;
    my ($self,$userName,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
    if($message eq &#039;Hello&#039;) {&lt;br /&gt;
      &lt;br /&gt;
      # We send our wonderful Hello World message&lt;br /&gt;
      sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
    # for other SPADS processing&lt;br /&gt;
    return 0;&lt;br /&gt;
    &lt;br /&gt;
  }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;lt;code&amp;gt;.conf&amp;lt;/code&amp;gt; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; will contain the list of forbidden words, and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; a global setting (unmodifiable, not impacted by preset change), and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; a preset setting (modifiable, can be impacted by preset change). The &amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;lt;code&amp;gt;MyConfigurablePlugin.conf&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.conf&amp;lt;/code&amp;gt; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;lt;code&amp;gt;MyConfigurablePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ForbiddenWords.pm&amp;lt;/code&amp;gt; and editing it to replace &amp;lt;code&amp;gt;MyConfigurablePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;ForbiddenWords&amp;lt;/code&amp;gt; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the &amp;lt;code&amp;gt;%globalPluginParams&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;%presetPluginParam&amp;lt;/code&amp;gt; declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our &amp;lt;code&amp;gt;SAIDBATTLE&amp;lt;/code&amp;gt; handler &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;lt;code&amp;gt;words&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;immuneLevel&amp;lt;/code&amp;gt; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;lt;code&amp;gt;hLobbySaidBattle&amp;lt;/code&amp;gt; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;lt;code&amp;gt;!plugin ForbiddenWords load&amp;lt;/code&amp;gt;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16144</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16144"/>
		<updated>2021-01-28T09:27:00Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Step 2: Using cpanminus to install Inline::Python */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 1: Installing cpanminus ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight module manager for Perl, which is able to install Perl modules directly from Git repositories and allows interactive mode installations.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use the generic default Perl module manager &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 2: Using cpanminus to install Inline::Python ==&lt;br /&gt;
As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so it is recommended to install the latest version instead. Unfortunately, the installation script included in the latest version on the module maintainer repository has some problems when installing on Windows. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If for some reason the installation of the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; fails, you can try to install the stable release instead. Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python installation detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; must be run in interactive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16143</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16143"/>
		<updated>2021-01-28T09:25:37Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Step 2: Using cpanminus to install Inline::Python */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 1: Installing cpanminus ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight module manager for Perl, which is able to install Perl modules directly from Git repositories and allows interactive mode installations.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use the generic default Perl module manager &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 2: Using cpanminus to install Inline::Python ==&lt;br /&gt;
As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so it is recommended to install the latest version instead. Unfortunately, the installation script included in the latest version on the module maintainer repository has some problems when installing on Windows. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If for some reason the installation from my fork fails, you can try to install the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; instead. Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python installation detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; must be run in interactive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16142</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16142"/>
		<updated>2021-01-28T09:19:22Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 1: Installing cpanminus ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight module manager for Perl, which is able to install Perl modules directly from Git repositories and allows interactive mode installations.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use the generic default Perl module manager &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 2: Using cpanminus to install Inline::Python ==&lt;br /&gt;
As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so it is recommended to install the latest version instead. Unfortunately, the installation script included in the latest version on the module maintainer repository has some problems when installing on Windows. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If for some reason the installation from my fork fails, you can try to install the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; instead. Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python libraries detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; must be run in interactive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16141</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16141"/>
		<updated>2021-01-28T09:04:26Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Installing Inline::Python on Windows */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 1: Installing cpanminus ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight module manager for Perl, which is able to install Perl modules directly from Git repositories and allows interactive mode installations.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use the generic default Perl module manager &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Step 2: Using cpanminus to install Inline::Python ==&lt;br /&gt;
As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so it is recommended to install the latest version instead. Unfortunately, the installation script included in the latest version on the module maintainer repository has some problems when installing on Windows. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
If for some reason the installation from my fork fails, you can try to install the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; instead. Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python libraries detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; must be run in ineractive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16252</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16252"/>
		<updated>2021-01-28T07:41:43Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Technical prerequisites */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install the latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16202</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16202"/>
		<updated>2021-01-28T07:32:38Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  sub onPrivateMsg {&lt;br /&gt;
    &lt;br /&gt;
    # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    # $userName is the name of the user sending the private message&lt;br /&gt;
    # $message is the message sent by the user&lt;br /&gt;
    my ($self,$userName,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
    if($message eq &#039;Hello&#039;) {&lt;br /&gt;
      &lt;br /&gt;
      # We send our wonderful Hello World message&lt;br /&gt;
      sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
    # for other SPADS processing&lt;br /&gt;
    return 0;&lt;br /&gt;
    &lt;br /&gt;
  }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16251</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16251"/>
		<updated>2021-01-28T07:31:33Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16250</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16250"/>
		<updated>2021-01-28T07:29:57Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. It is recommended to install latest version for SPADS, refer to [https://springrts.com/wiki/SPADS_Inline::Python_installation_guide this guide] for installation.&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16140</id>
		<title>SPADS Inline::Python installation guide</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_Inline::Python_installation_guide&amp;diff=16140"/>
		<updated>2021-01-28T07:18:25Z</updated>

		<summary type="html">&lt;p&gt;Bibim: Created page with &amp;quot;= Introduction =  &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; is a Perl module which allows interactions between Perl code and Python code. SPADS uses it to communicate with plugins coded in Python.&lt;br /&gt;
&lt;br /&gt;
Unfortunately the current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and SPADS plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this document will detail the different ways to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, on Linux and Windows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Linux =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, or for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository. This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using the package manager of your Linux distribution ==&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained in introduction. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 3: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods failed.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
= Installing Inline::Python on Windows =&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 1: Using cpanminus to install latest version from GitHub ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have checked that &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, you are ready to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;. Unfortunately, the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on the module maintainer repository has some problems regarding Windows installation. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
== Method 2: Using cpan ==&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. However, only the current stable releases of the modules can easily be installed this way. As explained in introduction, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so this method is only recommended if you don&#039;t plan to use Python 3 or if the previous installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; failed.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; doesn&#039;t install automatically on Windows due to broken Python libraries detection and one test which is coded for Linux system only and which must be skipped. Consequently, &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; must be run in ineractive mode and with a flag to force installation despite the failing test, using this command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;cpanm --interactive -f Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The install script will then ask 4 questions:&lt;br /&gt;
# &amp;lt;code&amp;gt;Use which? [1]&amp;lt;/code&amp;gt; Here you must enter the full path of your &amp;lt;code&amp;gt;python.exe&amp;lt;/code&amp;gt; file from your Python installation (warning: paths containing spaces are not supported by the installer). For example: &amp;lt;code&amp;gt;C:\Python\python.exe&amp;lt;/code&amp;gt; &lt;br /&gt;
# &amp;lt;code&amp;gt;Enter extra libraries (e.g. -lfoo -lbar) [None]&amp;lt;/code&amp;gt; Here you can press enter, as no additional library should be needed for standard Python installation.&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you need to enter the digit corresponding to the library with 2 digits in its name. For example: &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; (to select entry &amp;lt;code&amp;gt;2) C:/Python/libs/python39.lib&amp;lt;/code&amp;gt;)&lt;br /&gt;
# &amp;lt;code&amp;gt;Which? Or enter another. [1]&amp;lt;/code&amp;gt; Here you can press enter as the install script should have auto-detected the include location of your Python installation correctly.&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16128</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS&amp;diff=16128"/>
		<updated>2021-01-28T06:27:27Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Development */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;span style=&amp;quot;font-weight:bold; font-size:large;&amp;quot;&amp;gt;SPADS links&amp;lt;/span&amp;gt;&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
*[[SPADS:Installation|Installation]]&lt;br /&gt;
*[https://springrts.com/phpbb/viewforum.php?f=88 Discussion in Spring forums]&lt;br /&gt;
*[http://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_official_plugins_list Official plugins list]&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_update_procedure_for_new_Spring_version SPADS update procedure for new Spring version]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/UPDATE SPADS manual update procedure for new major SPADS version]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_install_notes Plugin installation notes]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_PerlUnitSync_module_generation_for_Windows PerlUnitSync module generation for Windows]&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development Plugin development tutorials (Perl)]&lt;br /&gt;
*[http://springrts.com/wiki/SPADS_plugin_development_(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16249</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16249"/>
		<updated>2021-01-27T21:11:29Z</updated>

		<summary type="html">&lt;p&gt;Bibim: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. The current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and the plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the SPADS Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this section will detail the different ways to install the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; prerequisite, on Linux and on Windows.&lt;br /&gt;
&lt;br /&gt;
== Installing Inline::Python on Linux ==&lt;br /&gt;
&lt;br /&gt;
=== Method 1: Using cpanminus to install latest version from GitHub ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository.&amp;lt;br&amp;gt;&lt;br /&gt;
This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: Using the package manager of your Linux distribution ===&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained above. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; fails.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 3: Using cpan ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods fail.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl): &amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Installing Inline::Python on Windows ==&lt;br /&gt;
&lt;br /&gt;
=== Method 1: Using cpanminus to install latest version from GitHub ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have checked that &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, you are ready to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;. Unfortunately, the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on the module maintainer repository has some problems regarding Windows installation. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: Using cpan ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. However, only the current stable releases of the modules can easily be installed this way. As explained above, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so this method is only recommended if you don&#039;t plan to use Python 3 or if the previous installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; fails.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command: &amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#ff0000&amp;quot;&amp;gt;&#039;&#039;&#039;/!\ WORK IN PROGRESS /!\&#039;&#039;&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16201</id>
		<title>SPADS plugin development</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development&amp;diff=16201"/>
		<updated>2021-01-27T21:07:01Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Perl plugin development only (for Python plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development_(Python) here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Perl basics before starting Perl plugin development (a brief introduction to Perl is available [https://perldoc.perl.org/perlintro here]).&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.py&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Python versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/MySimplePlugin.pm the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;MySimplePlugin.pm&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  sub onPrivateMsg {&lt;br /&gt;
    &lt;br /&gt;
    # $self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    # $userName is the name of the user sending the private message&lt;br /&gt;
    # $message is the message sent by the user&lt;br /&gt;
    my ($self,$userName,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
    if($message eq &#039;Hello&#039;) {&lt;br /&gt;
      &lt;br /&gt;
      # We send our wonderful Hello World message&lt;br /&gt;
      sayPrivate($userName,&#039;Hello World&#039;);&lt;br /&gt;
      &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
    # for other SPADS processing&lt;br /&gt;
    return 0;&lt;br /&gt;
    &lt;br /&gt;
  }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;HelloWorld.pm&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/HelloWorld.pm this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;perl&amp;quot;&amp;gt;&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
	<entry>
		<id>https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16248</id>
		<title>SPADS plugin development (Python)</title>
		<link rel="alternate" type="text/html" href="https://springrts-wiki.p2004a.com/mediawiki/index.php?title=SPADS_plugin_development_(Python)&amp;diff=16248"/>
		<updated>2021-01-27T21:06:45Z</updated>

		<summary type="html">&lt;p&gt;Bibim: /* Writing plugin code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;/!\ IN PROGRESS /!\&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
&lt;br /&gt;
SPADS is an autohost system coded in Perl, it supports both Perl and Python plugins. This tutorial deals with Python plugin development only (for Perl plugin development another tutorial is available [https://springrts.com/wiki/SPADS_plugin_development here]).&lt;br /&gt;
&lt;br /&gt;
SPADS plugins are object-oriented modules. It is strongly recommended to already know Python basics before starting Python plugin development (a Python tutorial is available [https://docs.python.org/3/tutorial/ here]).&lt;br /&gt;
&lt;br /&gt;
= Technical prerequisites =&lt;br /&gt;
&lt;br /&gt;
Python (the Python interpreter and its libraries) must be installed on the system (versions 2.6+ or 3.2+ recommended). The type of installation (32-bit / 64-bit) must be consistent with the Perl version and the Spring engine used for SPADS.&lt;br /&gt;
&lt;br /&gt;
Additionally, in order to allow communication between Perl code (SPADS core) and Python code (SPADS plugins), SPADS uses a Perl module named &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; which must also be installed on the system. The current stable release of this module suffers from a bug which makes Perl strings appear as byte strings when used with Python 3 (Python 2 is unaffected). This is not a blocking problem, as Python can easily convert these data if needed and the plugin API offers dedicated functions to deal with the problem (cf the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#Perl-strings-conversions PYTHON SPECIFIC NOTES - Perl strings conversions] section of the SPADS Plugin API documentation). However, a cleaner way to solve this problem is to install a fixed version of the module, directly from the official module maintainer repository. The remaining of this section will detail the different ways to install the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; prerequisite, on Linux and on Windows.&lt;br /&gt;
&lt;br /&gt;
== Installing Inline::Python on Linux ==&lt;br /&gt;
&lt;br /&gt;
=== Method 1: Using cpanminus to install latest version from GitHub ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
The first step consists in installing &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; (if not already installed on your system). To do that you can usually use the package manager of your Linux distribution. For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install cpanminus&amp;lt;/code&amp;gt;, for Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-App-cpanminus&amp;lt;/code&amp;gt; (for other cases, check [https://metacpan.org/pod/App::cpanminus#INSTALLATION the INSTALLATION section of cpanminus documentation]).&lt;br /&gt;
&lt;br /&gt;
Once &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, it can be used to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; from module maintainer&#039;s repository.&amp;lt;br&amp;gt;&lt;br /&gt;
This can be done simply by entering following command (with root privileges if you are using the system Perl):&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/niner/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: Using the package manager of your Linux distribution ===&lt;br /&gt;
This is the easiest way to install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; (if your distribution supports it), as it automatically downloads and installs a package specifically prepared for your distribution. However, this method installs the stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;, which suffers from the string conversion bug with Python 3 explained above. So this method is only recommended if you don&#039;t plan to use Python 3 or if the installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; fails.&lt;br /&gt;
&lt;br /&gt;
For this method you just need to find the name of the system package containing the &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; Perl module and install it on your system.&amp;lt;br&amp;gt;&lt;br /&gt;
For example, for Debian based distributions: &amp;lt;code&amp;gt;apt-get install libinline-python-perl&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
For Red Hat based distributions: &amp;lt;code&amp;gt;dnf install perl-Inline-Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 3: Using cpan ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. Unfortunately, only the current stable releases of the modules can easily be installed this way, so this method should only be reserved to cases where all other installation methods fail.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command (with root privileges if you are using the system Perl): &amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Installing Inline::Python on Windows ==&lt;br /&gt;
&lt;br /&gt;
=== Method 1: Using cpanminus to install latest version from GitHub ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is a lightweight package manager for Perl, which is able to install Perl modules directly from Git repositories. This is very useful for &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; installation because only the latest version from the module maintainer repository includes the fix for Perl string conversions with Python 3.&lt;br /&gt;
&lt;br /&gt;
If your are using Strawberry Perl, &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; should be pre-installed (you can check it by trying the &amp;lt;code&amp;gt;cpanm&amp;lt;/code&amp;gt; command in a console windows). If you are using another Perl distribution and &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; isn&#039;t pre-installed, you should be able to use the packet manager of your Perl distribution (&amp;lt;code&amp;gt;ppm&amp;lt;/code&amp;gt; for example for old ActiveState Perl releases) to install &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; to install it using following command: &amp;lt;code&amp;gt;cpan cpanminus&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you have checked that &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; is installed, you are ready to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt;. Unfortunately, the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on the module maintainer repository has some problems regarding Windows installation. I created my own fork on GitHub to fix these installation problems. As long as the module maintainer didn&#039;t merge my fixes in his repository, I recommend using my fork to install the latest version of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; on Windows, using following command:&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;cpanm https://github.com/Yaribz/inline-python-pm.git&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method 2: Using cpan ===&lt;br /&gt;
&amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt; is the standard tool included with Perl to install modules from the [https://www.cpan.org/ Comprehensive Perl Archive Network]. However, only the current stable releases of the modules can easily be installed this way. As explained above, the current stable release of &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; suffers from the string conversion bug with Python 3, so this method is only recommended if you don&#039;t plan to use Python 3 or if the previous installation method based on &amp;lt;code&amp;gt;cpanminus&amp;lt;/code&amp;gt; fails.&lt;br /&gt;
&lt;br /&gt;
To install &amp;lt;code&amp;gt;Inline::Python&amp;lt;/code&amp;gt; using &amp;lt;code&amp;gt;cpan&amp;lt;/code&amp;gt;, simply enter following command: &amp;lt;code&amp;gt;cpan Inline::Python&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Simple plugin tutorial (HelloWorld) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first SPADS plugin. So let&#039;s start simple with a good old &amp;quot;Hello world&amp;quot;. The plugin will answer &amp;quot;Hello World&amp;quot; to anyone saying &amp;quot;Hello&amp;quot; in a private message to SPADS.&lt;br /&gt;
&lt;br /&gt;
== Choosing our template ==&lt;br /&gt;
&lt;br /&gt;
Some plugin templates are available to help you bootstrap your plugin development. The commented versions of these templates are available [http://planetspads.free.fr/spads/plugins/templates/commented/ here], while the raw versions (without comment) are available [http://planetspads.free.fr/spads/plugins/templates/raw/ here] (the &amp;lt;code&amp;gt;.pm&amp;lt;/code&amp;gt; files must be ignored for this tutorial, these are the Perl versions of the templates).&lt;br /&gt;
&lt;br /&gt;
Our first plugin will be very basic, so we will use the simplest template: &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt;. Let&#039;s download [http://planetspads.free.fr/spads/plugins/templates/commented/mysimpleplugin.py the commented version of this template] and open it with our favorite editor. As you can see, the code is heavily commented (actually every single line of code is explained), so I won&#039;t go into further details here. Now that you&#039;ve read and understood the commented template, let&#039;s actually start plugin development.&lt;br /&gt;
&lt;br /&gt;
== Adapting the template ==&lt;br /&gt;
&lt;br /&gt;
First we must name our plugin. Let&#039;s call it &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt;. We have to rename the downloaded template from &amp;lt;code&amp;gt;mysimpleplugin.py&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;, and edit it to replace &amp;lt;code&amp;gt;MySimplePlugin&amp;lt;/code&amp;gt; by &amp;lt;code&amp;gt;HelloWorld&amp;lt;/code&amp;gt; in the source code.&lt;br /&gt;
&lt;br /&gt;
== Checking SPADS plugin system ==&lt;br /&gt;
&lt;br /&gt;
Before going into actually writing new code, let&#039;s just check that the plugin works in SPADS (if not done yet, you have to configure the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory] in your &amp;lt;code&amp;gt;spads.conf&amp;lt;/code&amp;gt; configuration file and reload SPADS configuration).&lt;br /&gt;
&lt;br /&gt;
We have to move our new plugin (&amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt;) in the SPADS plugins directory so that SPADS can find it. Then, as a privileged SPADS user, we can type following command (in a private message to SPADS for example): &amp;lt;code&amp;gt;!plugin HelloWorld load&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Loaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been loaded successfully.&lt;br /&gt;
&lt;br /&gt;
If all is ok, we can let this SPADS instance running like this, we will get back to it later.&lt;br /&gt;
&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
Writing plugin code mainly consists in implementing plugin callbacks and calling plugin API functions, as specified in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation].&lt;br /&gt;
&lt;br /&gt;
So we want our plugin to react to some private messages sent to SPADS. To do so we have to implement a plugin callback function which is called by SPADS core each time a lobby private message is received: this is the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onPrivateMsg-self-userName-message onPrivateMsg] event-based callback.&lt;br /&gt;
&lt;br /&gt;
We also want our plugin to send a private message, to do so we can call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayPrivate-user-message sayPrivate].&lt;br /&gt;
&lt;br /&gt;
Now that we have identified the API functions that we need, we can actually implement our plugin. Here is a commented implementation example of the &amp;lt;code&amp;gt;onPrivateMsg&amp;lt;/code&amp;gt; callback, which will answer &amp;lt;code&amp;gt;Hello World&amp;lt;/code&amp;gt; to anyone saying &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; in a private message to SPADS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    # This is the callback called each time SPADS receives a private message&lt;br /&gt;
    #   self is the plugin object (first parameter of all plugin callbacks)&lt;br /&gt;
    #   userName is the name of the user sending the private message&lt;br /&gt;
    #   message is the message sent by the user&lt;br /&gt;
    def onPrivateMsg(self,userName,message):&lt;br /&gt;
    &lt;br /&gt;
        # Here we &amp;quot;fix&amp;quot; strings received from Perl in case&lt;br /&gt;
        # the Inline::Python module transmits them as byte strings&lt;br /&gt;
        (userName,message)=spads.fix_string(userName,message)&lt;br /&gt;
   &lt;br /&gt;
        # We check the message sent by the user is &amp;quot;Hello&amp;quot;&lt;br /&gt;
        if message == &#039;Hello&#039;:&lt;br /&gt;
            &lt;br /&gt;
            # We send our wonderful Hello World message&lt;br /&gt;
            spads.sayPrivate(userName,&#039;Hello World&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # We return 0 because we don&#039;t want to filter out private messages&lt;br /&gt;
        # for other SPADS processing&lt;br /&gt;
        return 0&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All we have to do now is adding this callback declaration in our &amp;lt;code&amp;gt;helloworld.py&amp;lt;/code&amp;gt; file located in SPADS plugins directory. We obtain [http://planetspads.free.fr/spads/plugins/tutorials/helloworld.py this fully functional HelloWorld plugin].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s test this plugin in SPADS. First, since we modified the plugin source code, we have to tell SPADS to reload the plugin as follows: &amp;lt;code&amp;gt;!plugin HelloWorld reload&amp;lt;/code&amp;gt;. SPADS should answer &amp;lt;code&amp;gt;Reloaded plugin HelloWorld.&amp;lt;/code&amp;gt;, which indicates the plugin has been reloaded successfully.&lt;br /&gt;
&lt;br /&gt;
Finally, just say &amp;lt;code&amp;gt;Hello&amp;lt;/code&amp;gt; to SPADS in a private message. Congratulations for your first SPADS plugin! ;)&lt;br /&gt;
&lt;br /&gt;
= Configurable plugin tutorial (ForbiddenWords) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write our first configurable SPADS plugin (a configurable plugin has its own configuration file, named after the plugin name but with &amp;quot;.conf&amp;quot; extension). This plugin will monitor all messages said by players in the battle lobby, and will kick players who use swear words.&lt;br /&gt;
&lt;br /&gt;
== Specifying our configuration parameters ==&lt;br /&gt;
&lt;br /&gt;
The first step of writing a configurable plugin is to choose how we will configure it. In our example, we will use 2 configuration parameters: &amp;quot;words&amp;quot; will contain the list of forbidden words, and &amp;quot;immuneLevel&amp;quot; will contain the minimum autohost access level to be immune regarding these forbidden words checks.&lt;br /&gt;
&lt;br /&gt;
We choose to make &amp;quot;words&amp;quot; a global setting (unmodifiable, not impacted by preset change), and &amp;quot;immuneLevel&amp;quot; a preset setting (modifiable, can be impacted by preset change). The &amp;quot;words&amp;quot; global setting will have no restriction (can be empty, can contain any character...), whereas the &amp;quot;immuneLevel&amp;quot; preset setting will only be allowed to be an integer or integer range.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
Once we have a clear view of our configuration settings, we can start adapting the template for our needs. Since we are making a configurable plugin, this time we will download [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.pm the configurable plugin template] and its associated [http://planetspads.free.fr/spads/plugins/templates/commented/MyConfigurablePlugin.conf configuration file example].&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s take a look at the configuration file example. This is a just a basic configuration file containing one global setting example and one preset setting example. Let&#039;s modify this file to match the configuration we chose as follows (don&#039;t forget to rename the file from &amp;quot;MyConfigurablePlugin.conf&amp;quot; to &amp;quot;ForbiddenWords.conf&amp;quot; also):&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is our global setting (can&#039;t be changed without reloading the configuration)&lt;br /&gt;
  words:ass;asshole;bastard;bitch;cunt;fuck;motherfucker;shit;whore&lt;br /&gt;
  &lt;br /&gt;
  # We must define our preset setting in the default preset at least&lt;br /&gt;
  [default]&lt;br /&gt;
  &lt;br /&gt;
  # This is our preset setting, which can be changed with &amp;quot;!plugin ... set ...&amp;quot;&lt;br /&gt;
  # or by loading a preset.&lt;br /&gt;
  # &amp;quot;100&amp;quot; is the default value, and any integer between 0 and 140 is allowed&lt;br /&gt;
  immuneLevel:100|0-140&lt;br /&gt;
|}&lt;br /&gt;
Then we must prepare the plugin template itself, by renaming it from &amp;quot;MyConfigurablePlugin.pm&amp;quot; to &amp;quot;ForbiddenWords.pm&amp;quot; and editing it to replace &amp;quot;MyConfigurablePlugin&amp;quot; by &amp;quot;ForbiddenWords&amp;quot; in the source code. We must also adapt the template so that it uses the configuration settings we chose. This is done by modifying the %globalPluginParams and %presetPluginParam declarations as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # We define one global setting &amp;quot;words&amp;quot; and one preset setting &amp;quot;immuneLevel&amp;quot;.&lt;br /&gt;
  # &amp;quot;words&amp;quot; has no type associated (no restriction on allowed values)&lt;br /&gt;
  # &amp;quot;immuneLevel&amp;quot; must be an integer or an integer range&lt;br /&gt;
  # (check %paramTypes hash in SpadsConf.pm for a complete list of allowed&lt;br /&gt;
  # setting types)&lt;br /&gt;
  my %globalPluginParams = ( words =&amp;gt; [] );&lt;br /&gt;
  my %presetPluginParams = ( immuneLevel =&amp;gt; [&#039;integer&#039;,&#039;integerRange&#039;] );&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
We want our plugin to react to messages said in the battle lobby. There is no dedicated callback for this event in [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html SPADS plugin API documentation], so we have to set up our own handler on the [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#SAIDBATTLE:server SAIDBATTLE lobby command]. To do so we have to call the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addLobbyCommandHandler-handlers-priority-caller addLobbyCommandHandler]. We will call this function at the end of our plugin constructor as follows, so that it will be set up directly when the plugin is loaded:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
    [...]&lt;br /&gt;
    &lt;br /&gt;
    # We set up a lobby command handler on SAIDBATTLE&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
    # We return the instantiated plugin&lt;br /&gt;
    return $self;&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
However, if SPADS is disconnected from the lobby due to network problems for example, all lobby handlers are automatically removed. So we must re-add them each time we connect to lobby server. This can be done using the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onLobbyConnected-self-lobbyInterface onLobbyConnected] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called each time we (re)connect to the lobby server&lt;br /&gt;
  sub onLobbyConnected {&lt;br /&gt;
    &lt;br /&gt;
    # When we are disconnected from the lobby server, all lobby command&lt;br /&gt;
    # handlers are automatically removed, so we (re)set up our command&lt;br /&gt;
    # handler here.&lt;br /&gt;
    addLobbyCommandHandler({SAIDBATTLE =&amp;gt; \&amp;amp;hLobbySaidBattle});&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Also, it is a good practice to remove any handler we have added when the plugin is unloaded. To do so we must implement the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback as follows:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This callback is called when the plugin is unloaded&lt;br /&gt;
  sub onUnload {&lt;br /&gt;
    &lt;br /&gt;
    # We remove our lobby command handler when the plugin is unloaded&lt;br /&gt;
    removeLobbyCommandHandler([&#039;SAIDBATTLE&#039;]);&lt;br /&gt;
    &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Finally, we have to implement our SAIDBATTLE handler &amp;quot;hLobbySaidBattle&amp;quot;. In this handler we need to perform following operations:&lt;br /&gt;
* skip processing if the user is the autohost itself: we need to access SPADS configuration to compare the user name with the [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:lobbyLogin lobbyLogin] setting value. So we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getSpadsConf getSpadsConf].&lt;br /&gt;
* perform processing according to our configuration (&amp;quot;words&amp;quot; and &amp;quot;immuneLevel&amp;quot; settings): we need to access our plugin configuration, so we will need the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getPluginConf-pluginName-caller getPluginConf].&lt;br /&gt;
* retrieve the autohost access level of the user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#getUserAccessLevel-user getUserAccessLevel]&lt;br /&gt;
* send a message to the battle lobby when we kick someone: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#sayBattle-message sayBattle].&lt;br /&gt;
* send a [http://springrts.com/dl/LobbyProtocol/ProtocolDescription.html#KICKFROMBATTLE:client KICKFROMBATTLE] lobby command to kick a user: we will use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#queueLobbyCommand-lobbyCommand queueLobbyCommand].&lt;br /&gt;
&lt;br /&gt;
Here is a commented implementation example of this &amp;quot;hLobbySaidBattle&amp;quot; handler, which will kick any non-privileged user saying a forbidden word in the battle lobby:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler we set up on SAIDBATTLE lobby command.&lt;br /&gt;
  # It is called each time a player says something in the battle lobby.&lt;br /&gt;
  sub hLobbySaidBattle {&lt;br /&gt;
    &lt;br /&gt;
    # $command is the lobby command name (SAIDBATTLE)&lt;br /&gt;
    # $user is the name of the user who said something in the battle lobby&lt;br /&gt;
    # $message is the message said in the battle lobby&lt;br /&gt;
    my ($command,$user,$message)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # First we check it&#039;s not a message from SPADS (so we don&#039;t kick ourself)&lt;br /&gt;
    my $p_spadsConf=getSpadsConf();&lt;br /&gt;
    return if($user eq $p_spadsConf-&amp;gt;{lobbyLogin});&lt;br /&gt;
    &lt;br /&gt;
    # Then we check the user isn&#039;t a privileged user&lt;br /&gt;
    # (autohost access level &amp;gt;= immuneLevel)&lt;br /&gt;
    my $p_conf=getPluginConf();&lt;br /&gt;
    return if(getUserAccessLevel($user) &amp;gt;= $p_conf-&amp;gt;{immuneLevel});&lt;br /&gt;
    &lt;br /&gt;
    # We put the forbidden words in a array&lt;br /&gt;
    my @forbiddenWords=split(/;/,$p_conf-&amp;gt;{words});&lt;br /&gt;
    &lt;br /&gt;
    # We test each forbidden word&lt;br /&gt;
    foreach my $forbiddenWord (@forbiddenWords) {&lt;br /&gt;
      &lt;br /&gt;
      # If the message contains the forbidden word (case insensitive)&lt;br /&gt;
      if($message =~ /\b$forbiddenWord\b/i) {&lt;br /&gt;
        &lt;br /&gt;
        # Then we kick the user from the battle lobby&lt;br /&gt;
        sayBattle(&amp;quot;Kicking $user from battle (watch your language!)&amp;quot;);&lt;br /&gt;
        queueLobbyCommand([&amp;quot;KICKFROMBATTLE&amp;quot;,$user]);&lt;br /&gt;
        &lt;br /&gt;
        # We quit the foreach loop (no need to test other forbidden word)&lt;br /&gt;
        last;&lt;br /&gt;
        &lt;br /&gt;
      }&lt;br /&gt;
    &lt;br /&gt;
    }&lt;br /&gt;
  &lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.pm this fully functional ForbiddenWords] plugin and [http://planetspads.free.fr/spads/plugins/tutorials/ForbiddenWords.conf its associated configuration file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the associated configuration file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin ForbiddenWords load&amp;quot;. And finally, as an unprivileged user we can try to say some forbidden words in the battle lobby and we should get kicked by the plugin.&lt;br /&gt;
&lt;br /&gt;
= New-command plugin tutorial (TimePlugin) =&lt;br /&gt;
&lt;br /&gt;
In this section we are going to write a plugin which implements a new command for SPADS. Such plugins are configurable plugins like the one we wrote just before, with 2 additional files to configure the new commands. This plugin will give current time when someone types &amp;quot;!time&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Preparing the template ==&lt;br /&gt;
&lt;br /&gt;
This time we need to download 4 files to prepare our plugin: [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.pm the new-command plugin template], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPlugin.conf the associated configuration file example], [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginHelp.dat the help file example] and [http://planetspads.free.fr/spads/plugins/templates/commented/MyNewCommandPluginCmd.conf the commands rights configuration file example].&lt;br /&gt;
&lt;br /&gt;
As usual, we rename these files to match our plugin name: &amp;quot;MyNewCommandPlugin&amp;quot; --&amp;gt; &amp;quot;TimePlugin&amp;quot;. Then we edit the plugin template TimePlugin.pm and replace &amp;quot;MyNewCommandPlugin&amp;quot; by &amp;quot;TimePlugin&amp;quot; in the source code, and we do the same for the plugin configuration template TimePlugin.conf, so that the &amp;quot;commandsFile&amp;quot; and &amp;quot;helpFile&amp;quot; settings are consistent with the files we just renamed.&lt;br /&gt;
&lt;br /&gt;
Then we can edit our command rights requirements configuration file: TimePluginCmd.conf. This file uses the same syntax as the standard SPADS commands.conf file. The template provides a default command &amp;quot;myCommand&amp;quot; with no requirement. We will just rename this command to &amp;quot;time&amp;quot;:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # Anyone can call our command from anywhere&lt;br /&gt;
  [time]&lt;br /&gt;
  ::|0:&lt;br /&gt;
|}&lt;br /&gt;
Now we need to write the help information for our new command. This is done in the file TimePluginHelp.dat. This file uses the same syntax as the standard SPADS help.dat file, and the template provides a help example for a command &amp;quot;myCommand&amp;quot; as a syntax reminder. After each command declaration, the first line is the command syntax description, and the other lines are optional usage examples. Let&#039;s replace this help example with our own help information for our basic !time command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [time]&lt;br /&gt;
  &amp;lt;nowiki&amp;gt;!&amp;lt;/nowiki&amp;gt;time - This command just prints current time&lt;br /&gt;
|}&lt;br /&gt;
== Writing plugin code ==&lt;br /&gt;
&lt;br /&gt;
The new-command plugin template that we used to initialize our plugin already defines a new command named &amp;quot;myCommand&amp;quot;, so all we have to do is to edit our plugin file ( TimePlugin.pm ) and replace this command by our own &amp;quot;time&amp;quot; command:&lt;br /&gt;
&lt;br /&gt;
First, let&#039;s modify the code which sets up the new SPADS command handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#addSpadsCommandHandler-handlers-replace-0 addSpadsCommandHandler]. This call is located in the plugin constructor. We edit it so that it becomes:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We declare our new command and the associated handler&lt;br /&gt;
  addSpadsCommandHandler({time =&amp;gt; \&amp;amp;hSpadsTime});&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
We must modify the same way the code which removes this SPADS handler using the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#removeSpadsCommandHandler-commands removeSpadsCommandHandler] in the [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#onUnload-self-context onUnload] event-based callback:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  [...]&lt;br /&gt;
  # We remove our new command handler&lt;br /&gt;
  removeSpadsCommandHandler([&#039;time&#039;]);&lt;br /&gt;
  [...]&lt;br /&gt;
|}&lt;br /&gt;
Finally, we must replace the handler example &amp;quot;hMyCommand&amp;quot; by our own handler &amp;quot;hSpadsTime&amp;quot;. In order to answer to the user issuing the command, we can use the plugin API function [http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html#answer-message answer], which will send an answer message to the user in the same way he sent the command (private message, battle lobby...).&lt;br /&gt;
&lt;br /&gt;
Here is an implementation example for this basic command:&lt;br /&gt;
{|style=&amp;quot;margin-top: 10px; margin-left: 40px; background-color: #38474E; border: 1px solid black; border-color: #20292E&amp;quot; |&lt;br /&gt;
|&lt;br /&gt;
  # This is the handler for our new command&lt;br /&gt;
  sub hSpadsTime {&lt;br /&gt;
    my ($source,$user,$p_params,$checkOnly)=@_;&lt;br /&gt;
    &lt;br /&gt;
    # time is a basic command, we have nothing to check in case of callvote&lt;br /&gt;
    return 1 if($checkOnly);&lt;br /&gt;
    &lt;br /&gt;
    my @time = localtime();&lt;br /&gt;
    @time = map(sprintf(&amp;quot;%02d&amp;quot;,$_),@time);&lt;br /&gt;
    &lt;br /&gt;
    answer(&amp;quot;Current local time: $time[2]:$time[1]:$time[0]&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Once we put all that together, we obtain [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.pm this fully functional TimePlugin] plugin, and its associated [http://planetspads.free.fr/spads/plugins/tutorials/TimePlugin.conf configuration file], [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginCmd.conf command rights configuration file] and [http://planetspads.free.fr/spads/plugins/tutorials/TimePluginHelp.dat command help file].&lt;br /&gt;
&lt;br /&gt;
== Testing our plugin ==&lt;br /&gt;
&lt;br /&gt;
To test our plugin, we have to put the plugin module and the plugin help file in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:pluginsDir SPADS plugins directory], and the 2 configuration files in [http://planetspads.free.fr/spads/doc/spadsDoc_All.html#global:etcDir SPADS etc directory].&lt;br /&gt;
&lt;br /&gt;
Then we load the plugin as follows: &amp;quot;!plugin TimePlugin load&amp;quot;. And finally, we can try to say !time in a private message to SPADS or in the battle lobby, and SPADS should answer giving current local time. Our !time command help should also appear in &amp;quot;!help&amp;quot; and &amp;quot;!help time&amp;quot; outputs.&lt;br /&gt;
&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Bibim</name></author>
	</entry>
</feed>