Index: ps/trunk/source/tools/lobbybots/setup.py
===================================================================
--- ps/trunk/source/tools/lobbybots/setup.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/setup.py (nonexistent)
@@ -1,42 +0,0 @@
-#!/usr/bin/env python3
-
-"""setup.py for 0ad XMPP lobby bots."""
-
-from setuptools import find_packages, setup
-
-setup(
- name='XpartaMuPP',
- version='0.24',
- description='Multiplayer lobby bots for 0ad',
- packages=find_packages(),
- entry_points={
- 'console_scripts': [
- 'echelon=xpartamupp.echelon:main',
- 'xpartamupp=xpartamupp.xpartamupp:main',
- 'echelon-db=xpartamupp.lobby_ranking:main',
- ]
- },
- install_requires=[
- 'dnspython',
- 'sleekxmpp',
- 'sqlalchemy',
- ],
- tests_require=[
- 'coverage',
- 'hypothesis',
- 'parameterized',
- ],
- classifiers=[
- 'Development Status :: 3 - Alpha',
- 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Topic :: Games/Entertainment',
- 'Topic :: Internet :: XMPP',
- ],
- zip_safe=False,
- test_suite='tests',
-)
Index: ps/trunk/source/tools/lobbybots/ejabberd_example.yml
===================================================================
--- ps/trunk/source/tools/lobbybots/ejabberd_example.yml (revision 27093)
+++ ps/trunk/source/tools/lobbybots/ejabberd_example.yml (nonexistent)
@@ -1,855 +0,0 @@
-###
-###' ejabberd configuration file
-###
-###
-
-### The parameters used in this configuration file are explained in more detail
-### in the ejabberd Installation and Operation Guide.
-### Please consult the Guide in case of doubts, it is included with
-### your copy of ejabberd, and is also available online at
-### http://www.process-one.net/en/ejabberd/docs/
-
-### The configuration file is written in YAML.
-### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
-### However, ejabberd treats different literals as different types:
-###
-### - unquoted or single-quoted strings. They are called "atoms".
-### Example: dog, 'Jupiter', '3.14159', YELLOW
-###
-### - numeric literals. Example: 3, -45.0, .0
-###
-### - quoted or folded strings.
-### Examples of quoted string: "Lizzard", "orange".
-### Example of folded string:
-### > Art thou not Romeo,
-### and a Montague?
----
-###. =======
-###' LOGGING
-
-##
-## loglevel: Verbosity of log files generated by ejabberd.
-## 0: No ejabberd log at all (not recommended)
-## 1: Critical
-## 2: Error
-## 3: Warning
-## 4: Info
-## 5: Debug
-##
-loglevel: 4
-
-##
-## rotation: Disable ejabberd's internal log rotation, as the Debian package
-## uses logrotate(8).
-log_rotate_size: 0
-log_rotate_date: ""
-
-##
-## overload protection: If you want to limit the number of messages per second
-## allowed from error_logger, which is a good idea if you want to avoid a flood
-## of messages when system is overloaded, you can set a limit.
-## 100 is ejabberd's default.
-log_rate_limit: 100
-
-##
-## watchdog_admins: Only useful for developers: if an ejabberd process
-## consumes a lot of memory, send live notifications to these XMPP
-## accounts.
-##
-## watchdog_admins:
-## - "bob@example.com"
-
-###. ===============
-###' NODE PARAMETERS
-
-##
-## net_ticktime: Specifies net_kernel tick time in seconds. This options must have
-## identical value on all nodes, and in most cases shouldn't be changed at all from
-## default value.
-##
-## net_ticktime: 60
-
-###. ================
-###' SERVED HOSTNAMES
-
-##
-## hosts: Domains served by ejabberd.
-## You can define one or several, for example:
-## hosts:
-## - "example.net"
-## - "example.com"
-## - "example.org"
-##
-hosts:
- - "localhost"
-
-##
-## route_subdomains: Delegate subdomains to other XMPP servers.
-## For example, if this ejabberd serves example.org and you want
-## to allow communication with an XMPP server called im.example.org.
-##
-## route_subdomains: s2s
-
-###. ============
-###' Certificates
-
-## List all available PEM files containing certificates for your domains,
-## chains of certificates or certificate keys. Full chains will be built
-## automatically by ejabberd.
-##
-certfiles:
- - "/etc/ejabberd/ejabberd.pem"
-
-## If your system provides only a single CA file (CentOS/FreeBSD):
-## ca_file: "/etc/ssl/certs/ca-bundle.pem"
-
-###. =================
-###' TLS configuration
-
-## Note that the following configuration is the default
-## configuration of the TLS driver, so you don't need to
-## uncomment it.
-##
-define_macro:
- 'TLS_CIPHERS': "HIGH:!aNULL:!eNULL:!3DES:@STRENGTH"
- 'TLS_OPTIONS':
- - "no_sslv2"
- - "no_sslv3"
- - "no_tlsv1"
- - "cipher_server_preference"
- - "no_compression"
- ## 'DH_FILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
-
-## c2s_dhfile: 'DH_FILE'
-## s2s_dhfile: 'DH_FILE'
-c2s_ciphers: 'TLS_CIPHERS'
-s2s_ciphers: 'TLS_CIPHERS'
-c2s_protocol_options: 'TLS_OPTIONS'
-s2s_protocol_options: 'TLS_OPTIONS'
-
-###. ===============
-###' LISTENING PORTS
-
-##
-## listen: The ports ejabberd will listen on, which service each is handled
-## by and what options to start it with.
-##
-listen:
- -
- port: 5222
- ip: "0.0.0.0"
- module: ejabberd_c2s
- starttls: true
- starttls_required: false
- protocol_options: 'TLS_OPTIONS'
- max_stanza_size: 1048576
- shaper: c2s_shaper
- access: c2s
-
- ## port: 5269
- ## ip: "::"
- ## module: ejabberd_s2s_in
-
- -
- port: 5280
- ip: "127.0.0.1"
- module: ejabberd_http
- request_handlers:
- "/ws": ejabberd_http_ws
- "/bosh": mod_bosh
- "/api": mod_http_api
- ## "/pub/archive": mod_http_fileserver
- web_admin: true
- ## register: true
- ## captcha: true
- tls: true
- protocol_options: 'TLS_OPTIONS'
-
- ##
- ## ejabberd_service: Interact with external components (transports, ...)
- ##
- ## -
- ## port: 8888
- ## ip: "::"
- ## module: ejabberd_service
- ## access: all
- ## shaper_rule: fast
- ## ip: "127.0.0.1"
- ## privilege_access:
- ## roster: "both"
- ## message: "outgoing"
- ## presence: "roster"
- ## delegations:
- ## "urn:xmpp:mam:1":
- ## filtering: ["node"]
- ## "http://jabber.org/protocol/pubsub":
- ## filtering: []
- ## hosts:
- ## "icq.example.org":
- ## password: "secret"
- ## "sms.example.org":
- ## password: "secret"
-
- ##
- ## ejabberd_stun: Handles STUN Binding requests
- ##
- -
- port: 3478
- transport: udp
- module: ejabberd_stun
-
- ##
- ## To handle XML-RPC requests that provide admin credentials:
- ##
- ## -
- ## port: 4560
- ## ip: "::"
- ## module: ejabberd_xmlrpc
- ## maxsessions: 10
- ## timeout: 5000
- ## access_commands:
- ## admin:
- ## commands: all
- ## options: []
-
- ##
- ## To enable secure http upload
- ##
- ## -
- ## port: 5444
- ## ip: "::"
- ## module: ejabberd_http
- ## request_handlers:
- ## "": mod_http_upload
- ## tls: true
- ## protocol_options: 'TLS_OPTIONS'
- ## dhfile: 'DH_FILE'
- ## ciphers: 'TLS_CIPHERS'
-
-## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
-## password storage (see auth_password_format option).
-disable_sasl_mechanisms: "digest-md5"
-
-###. ==================
-###' S2S GLOBAL OPTIONS
-
-##
-## s2s_use_starttls: Enable STARTTLS for S2S connections.
-## Allowed values are: false, optional or required
-## You must specify 'certfiles' option
-##
-s2s_use_starttls: required
-
-##
-## S2S whitelist or blacklist
-##
-## Default s2s policy for undefined hosts.
-##
-## s2s_access: s2s
-
-##
-## Outgoing S2S options
-##
-## Preferred address families (which to try first) and connect timeout
-## in seconds.
-##
-## outgoing_s2s_families:
-## - ipv4
-## - ipv6
-## outgoing_s2s_timeout: 190
-
-###. ==============
-###' AUTHENTICATION
-
-##
-## auth_method: Method used to authenticate the users.
-## The default method is the internal.
-## If you want to use a different method,
-## comment this line and enable the correct ones.
-##
-auth_method: internal
-
-##
-## Store the plain passwords or hashed for SCRAM:
-## auth_password_format: plain
-auth_password_format: scram
-##
-## Define the FQDN if ejabberd doesn't detect it:
-## fqdn: "server3.example.com"
-
-##
-## Authentication using external script
-## Make sure the script is executable by ejabberd.
-##
-## auth_method: external
-## extauth_program: "/path/to/authentication/script"
-
-##
-## Authentication using SQL
-## Remember to setup a database in the next section.
-##
-## auth_method: sql
-
-##
-## Authentication using PAM
-##
-## auth_method: pam
-## pam_service: "pamservicename"
-
-##
-## Authentication using LDAP
-##
-## auth_method: ldap
-##
-## List of LDAP servers:
-## ldap_servers:
-## - "lw"
-##
-## Encryption of connection to LDAP servers:
-## ldap_encrypt: none
-## ldap_encrypt: tls
-##
-## Port to connect to on LDAP servers:
-## ldap_port: 389
-## ldap_port: 636
-##
-## LDAP manager:
-## ldap_rootdn: "dc=example,dc=com"
-##
-## Password of LDAP manager:
-## ldap_password: "******"
-##
-## Search base of LDAP directory:
-## ldap_base: "dc=example,dc=com"
-##
-## LDAP attribute that holds user ID:
-## ldap_uids:
-## - "mail": "%u@mail.example.org"
-##
-## LDAP filter:
-## ldap_filter: "(objectClass=shadowAccount)"
-
-##
-## Anonymous login support:
-## auth_method: anonymous
-## anonymous_protocol: sasl_anon | login_anon | both
-## allow_multiple_connections: true | false
-##
-## host_config:
-## "public.example.org":
-## auth_method: anonymous
-## allow_multiple_connections: false
-## anonymous_protocol: sasl_anon
-##
-## To use both anonymous and internal authentication:
-##
-## host_config:
-## "public.example.org":
-## auth_method:
-## - internal
-## - anonymous
-
-###. ==============
-###' DATABASE SETUP
-
-## ejabberd by default uses the internal Mnesia database,
-## so you do not necessarily need this section.
-## This section provides configuration examples in case
-## you want to use other database backends.
-## Please consult the ejabberd Guide for details on database creation.
-
-##
-## MySQL server:
-##
-## sql_type: mysql
-## sql_server: "server"
-## sql_database: "database"
-## sql_username: "username"
-## sql_password: "password"
-##
-## If you want to specify the port:
-## sql_port: 1234
-
-##
-## PostgreSQL server:
-##
-## sql_type: pgsql
-## sql_server: "server"
-## sql_database: "database"
-## sql_username: "username"
-## sql_password: "password"
-##
-## If you want to specify the port:
-## sql_port: 1234
-##
-## If you use PostgreSQL, have a large database, and need a
-## faster but inexact replacement for "select count(*) from users"
-##
-## pgsql_users_number_estimate: true
-
-##
-## SQLite:
-##
-## sql_type: sqlite
-## sql_database: "/path/to/database.db"
-
-##
-## ODBC compatible or MSSQL server:
-##
-## sql_type: odbc
-## sql_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
-
-##
-## Number of connections to open to the database for each virtual host
-##
-## sql_pool_size: 10
-
-##
-## Interval to make a dummy SQL request to keep the connections to the
-## database alive. Specify in seconds: for example 28800 means 8 hours
-##
-## sql_keepalive_interval: undefined
-
-###. ===============
-###' TRAFFIC SHAPERS
-
-shaper:
- ##
- ## The "normal" shaper limits traffic speed to 1000 B/s
- ##
- normal: 1000
-
- ##
- ## The "fast" shaper limits traffic speed to 50000 B/s
- ##
- fast: 50000
-
-##
-## This option specifies the maximum number of elements in the queue
-## of the FSM. Refer to the documentation for details.
-##
-max_fsm_queue: 10000
-
-###. ====================
-###' ACCESS CONTROL LISTS
-acl:
- ##
- ## The 'admin' ACL grants administrative privileges to XMPP accounts.
- ## You can put here as many accounts as you want.
- ##
- admin:
- user:
- - "admin@localhost"
-
- ## Don't use a regex, to prevent others from obtaining permissions after registering such an account.
- bots:
- - user: "echelon23@localhost"
- - user: "wfgbot23@localhost"
-
- # Keep playernames short and easily typeable for everyone
- validname:
- user_regexp: "^[0-9A-Za-z._-]{1,20}$"
-
- ##
- ## Blocked users
- ##
- ## blocked:
- ## user:
- ## - "baduser@example.org"
- ## - "test"
-
- ## Local users: don't modify this.
- ##
- local:
- user_regexp: ""
-
- ##
- ## More examples of ACLs
- ##
- ## jabberorg:
- ## server:
- ## - "jabber.org"
- ## aleksey:
- ## user:
- ## - "aleksey@jabber.ru"
- ## test:
- ## user_regexp: "^test"
- ## user_glob: "test*"
-
- ##
- ## Loopback network
- ##
- loopback:
- ip:
- - "127.0.0.0/8"
- - "::1/128"
- - "::FFFF:127.0.0.1/128"
-
- ##
- ## Bad XMPP servers
- ##
- ## bad_servers:
- ## server:
- ## - "xmpp.zombie.org"
- ## - "xmpp.spam.com"
-
-##
-## Define specific ACLs in a virtual host.
-##
-## host_config:
-## "localhost":
-## acl:
-## admin:
-## user:
-## - "bob-local@localhost"
-
-###. ============
-###' SHAPER RULES
-
-shaper_rules:
- ## Maximum number of simultaneous sessions allowed for a single user:
- max_user_sessions: 10
- ## Maximum number of offline messages that users can have:
- max_user_offline_messages:
- - 5000: admin
- - 100
- ## For C2S connections, all users except admins use the "normal" shaper
- c2s_shaper:
- - none: admin
- - none: bots
- - normal
- ## All S2S connections use the "fast" shaper
- s2s_shaper: fast
-
-###. ============
-###' ACCESS RULES
-access_rules:
- ## This rule allows access only for local users:
- local:
- - allow: local
- ## Only non-blocked users can use c2s connections:
- c2s:
- - deny: blocked
- - allow
- ## Only admins can send announcement messages:
- announce:
- - allow: admin
- ## Only admins can use the configuration interface:
- configure:
- - allow: admin
- ## Expected by the ipstamp module for XpartaMuPP
- ipbots:
- - allow: bots
- muc_admin:
- - allow: admin
- ## Bots must be able to create nodes for games, ratings and boards lists
- pubsub_createnode:
- - allow: admin
- - allow: bots
- ## In-band registration allows registration of any possible username.
- ## To disable in-band registration, replace 'allow' with 'deny'.
- register:
- - deny: blocked
- - allow: validname
- ## Only allow to register from localhost
- trusted_network:
- - allow: loopback
- ## Do not establish S2S connections with bad servers
- ## If you enable this you also have to uncomment "s2s_access: s2s"
- ## s2s:
- ## - deny:
- ## - ip: "XXX.XXX.XXX.XXX/32"
- ## - deny:
- ## - ip: "XXX.XXX.XXX.XXX/32"
- ## - allow
-
-## ===============
-## API PERMISSIONS
-## ===============
-##
-## This section allows you to define who and using what method
-## can execute commands offered by ejabberd.
-##
-## By default "console commands" section allow executing all commands
-## issued using ejabberdctl command, and "admin access" section allows
-## users in admin acl that connect from 127.0.0.1 to execute all
-## commands except start and stop with any available access method
-## (ejabberdctl, http-api, xmlrpc depending what is enabled on server).
-##
-## If you remove "console commands" there will be one added by
-## default allowing executing all commands, but if you just change
-## permissions in it, version from config file will be used instead
-## of default one.
-##
-api_permissions:
- "console commands":
- from:
- - ejabberd_ctl
- who: all
- what: "*"
- "admin access":
- who:
- - access:
- - allow:
- - acl: loopback
- - acl: admin
- - oauth:
- - scope: "ejabberd:admin"
- - access:
- - allow:
- - acl: loopback
- - acl: admin
- what:
- - "*"
- - "!stop"
- - "!start"
- "public commands":
- who:
- - ip: "127.0.0.1/8"
- what:
- - "status"
- - "connected_users_number"
-
-## By default the frequency of account registrations from the same IP
-## is limited to 1 account every 10 minutes. To disable, specify: infinity
-registration_timeout: 3600
-
-##
-## Define specific Access Rules in a virtual host.
-##
-## host_config:
-## "localhost":
-## access:
-## c2s:
-## - allow: admin
-## - deny
-## register:
-## - deny
-
-###. ================
-###' DEFAULT LANGUAGE
-
-##
-## language: Default language used for server messages.
-##
-language: "en"
-
-##
-## Set a different default language in a virtual host.
-##
-## host_config:
-## "localhost":
-## language: "ru"
-
-###. =======
-###' CAPTCHA
-
-##
-## Full path to a script that generates the image.
-##
-## captcha_cmd: "/usr/share/ejabberd/captcha.sh"
-
-##
-## Host for the URL and port where ejabberd listens for CAPTCHA requests.
-##
-## captcha_host: "example.org:5280"
-
-##
-## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
-##
-## captcha_limit: 5
-
-###. ====
-###' ACME
-##
-## In order to use the acme certificate acquiring through "Let's Encrypt"
-## an http listener has to be configured to listen to port 80 so that
-## the authorization challenges posed by "Let's Encrypt" can be solved.
-##
-## A simple way of doing this would be to add the following in the listening
-## section and to configure port forwarding from 80 to 5281 either via NAT
-## (for ipv4 only) or using frontends such as haproxy/nginx/sslh/etc.
-## -
-## port: 5281
-## ip: "::"
-## module: ejabberd_http
-
-acme:
-
- ## A contact mail that the ACME Certificate Authority can contact in case of
- ## an authorization issue, such as a server-initiated certificate revocation.
- ## It is not mandatory to provide an email address but it is highly suggested.
- contact: "mailto:example-admin@example.com"
-
-
- ## The ACME Certificate Authority URL.
- ## This could either be:
- ## - https://acme-v01.api.letsencrypt.org - (Default) for the production CA
- ## - https://acme-staging.api.letsencrypt.org - for the staging CA
- ## - http://localhost:4000 - for a local version of the CA
- ca_url: "https://acme-v01.api.letsencrypt.org"
-
-###. =======
-###' MODULES
-
-##
-## Modules enabled in all ejabberd virtual hosts.
-##
-modules:
- mod_adhoc: {}
- mod_admin_extra: {}
- mod_announce: # recommends mod_adhoc
- access: announce
- mod_blocking: {} # requires mod_privacy
- mod_caps: {}
- mod_carboncopy: {}
- mod_client_state: {}
- mod_configure: {} # requires mod_adhoc
- ## mod_delegation: {} # for xep0356
- mod_disco: {}
- ## mod_echo: {}
- ## ipstamp module used by XpartaMuPP to insert IP addresses into the gamelist
- mod_ipstamp: {}
- ## mod_irc: {}
- mod_bosh: {}
- ## mod_http_fileserver:
- ## docroot: "/var/www"
- ## accesslog: "/var/log/ejabberd/access.log"
- ## mod_http_upload:
- ## # docroot: "@HOME@/upload"
- ## put_url: "https://@HOST@:5444"
- ## thumbnail: false # otherwise needs the identify command from ImageMagick installed
- ## mod_http_upload_quota:
- ## max_days: 30
- mod_last: {}
- ## XEP-0313: Message Archive Management
- ## You might want to setup a SQL backend for MAM because the mnesia database is
- ## limited to 2GB which might be exceeded on large servers
- ## mod_mam: {} # for xep0313, mnesia is limited to 2GB, better use an SQL backend
- mod_muc:
- ## host: "conference.@HOST@"
- access:
- - allow
- access_admin: muc_admin
- access_create: muc_admin
- access_persistent: muc_admin
- max_users: 5000
- default_room_options:
- allow_change_subj: false
- logging: true
- max_users: 1000
- persistent: true
- mod_muc_admin: {}
- mod_muc_log:
- outdir: "/lobby/logs"
- dirtype: plain
- file_format: plaintext
- timezone: universal
- ## mod_multicast: {}
- mod_offline:
- access_max_user_messages: max_user_offline_messages
- mod_ping:
- send_pings: true
- ## mod_pres_counter:
- ## count: 5
- ## interval: 60
- mod_privacy: {}
- mod_private: {}
- ## mod_proxy65: {}
- mod_pubsub:
- access_createnode: pubsub_createnode
- ## reduces resource comsumption, but XEP incompliant
- ignore_pep_from_offline: true
- ## XEP compliant, but increases resource comsumption
- ## ignore_pep_from_offline: false
- last_item_cache: false
- plugins:
- - "flat"
- - "hometree"
- - "pep" # pep requires mod_caps
- mod_push: {}
- mod_push_keepalive: {}
- mod_register:
- ##
- ## Protect In-Band account registrations with CAPTCHA.
- ##
- ## captcha_protected: true
- ##
- ## Set the minimum informational entropy for passwords.
- ##
- ## password_strength: 32
- ##
- ## After successful registration, the user receives
- ## a message with this subject and body.
- ##
- ## welcome_message:
- ## subject: "Welcome!"
- ## body: |-
- ## Hi.
- ## Welcome to this XMPP server.
- ##
- ## When a user registers, send a notification to
- ## these XMPP accounts.
- ##
- ## registration_watchers:
- ## - "admin1@example.org"
- ##
- ## Only clients in the server machine can register accounts
- ##
- ## ip_access: trusted_network
- ##
- ## Local c2s or remote s2s users cannot register accounts
- ##
- ## access_from: deny
- access: register
- mod_roster:
- versioning: true
- ## mod_shared_roster: {}
- mod_stats: {}
- mod_time: {}
- ## mod_vcard:
- ## search: false
- ## mod_vcard_xupdate: {}
- ## Convert all avatars posted by Android clients from WebP to JPEG
- ## mod_avatar: # this module needs compile option --enable-graphics
- ## convert:
- ## webp: jpeg
- mod_version: {}
- mod_stream_mgmt:
- resend_on_timeout: if_offline
- ## Non-SASL Authentication (XEP-0078) is now disabled by default
- ## because it's obsoleted and is used mostly by abandoned
- ## client software
- ## mod_legacy_auth: {}
- ## The module for S2S dialback (XEP-0220). Please note that you cannot
- ## rely solely on dialback if you want to federate with other servers,
- ## because a lot of servers have dialback disabled and instead rely on
- ## PKIX authentication. Make sure you have proper certificates installed
- ## and check your accessibility at https://check.messaging.one/
- mod_s2s_dialback: {}
- mod_http_api: {}
-
-##
-## Enable modules with custom options in a specific virtual host
-##
-## host_config:
-## "localhost":
-## modules:
-## mod_echo:
-## host: "mirror.localhost"
-
-##
-## Enable modules management via ejabberdctl for installation and
-## uninstallation of public/private contributed modules
-## (enabled by default)
-##
-
-allow_contrib_modules: true
-
-###.
-###'
-### Local Variables:
-### mode: yaml
-### End:
-### vim: set filetype=yaml tabstop=8 foldmarker=###',###. foldmethod=marker:
-
Property changes on: ps/trunk/source/tools/lobbybots/ejabberd_example.yml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/README.md
===================================================================
--- ps/trunk/source/tools/lobbybots/README.md (revision 27093)
+++ ps/trunk/source/tools/lobbybots/README.md (nonexistent)
@@ -1,641 +0,0 @@
-# 0 A.D. / Pyrogenesis Multiplayer Lobby Setup
-
-This README explains how to setup a custom Pyrogenesis Multiplayer Lobby server that can be used with the Pyrogenesis game.
-
-## Service description
-The Pyrogenesis Multiplayer Lobby consists of three components:
-
-* **XMPP server: ejabberd**:
- The XMPP server provides the platform where users can register accounts, chat in a public room, and can interact with lobby bots.
- ejabberd is recommended.
-
-* **Gamelist bot: XpartaMuPP**:
- This bot allows players to host and join online multiplayer matches.
-
-* **Rating bot: EcheLOn**:
- This bot allows players to gain a rating that reflects their skill based on online multiplayer matches.
- It is by no means necessary for the operation of a lobby in terms of match-making and chatting.
-
-## Service choices
-Before installing the service, you have to make some decisions:
-
-#### Choice: Domain Name
-Decide on a domain name where the service will be provided.
-This document will use `lobby.wildfiregames.com` as an example.
-If you intend to use the server only for local testing, you may choose `localhost`.
-
-#### Choice: Rating service
-Decide whether or not you want to employ the rating service.
-If you decide to not provide the rating service, you may skip the instructions for the rating bot in this document.
-
-#### Choice: Pyrogenesis version compatibility
-Decide whether you want to support serving multiple Pyrogenesis versions.
-
-Serving multiple versions of Pyrogenesis allows for seamless version upgrading on the backend and
-allows players that don't have the most recent version of Pyrogenesis yet to continue to play until
-the new release is available for their platform (applies mostly to linux distributions).
-
-If you decide to do so, you should use a naming pattern that includes the targetted Pyrogenesis version.
-For example to provide a Multiplayer Lobby for Pyrogenesis Alpha 23 "Ken Wood",
-name the lobby room `arena23` instead of `arena` and use `xpartamupp23` and `echelon23` as lobby bot names.
-Then when a version 24 of Pyrogenesis is employed, you can easily add `arena24`, `xpartamupp24` and `echelon24`.
-If you only want to use the service for local testing, you can stick to a single room and a single gamelist and rating bot.
-
-## 1. Install dependencies
-
-This section explains how to install the required software on a Debian-based linux distribution.
-For other operating systems, use the according package manager or consult the official documentation of the software.
-
-### 1.1 Install ejabberd
-
-The version requirement for ejabberd is 17.03 or later (due to the ipstamp module format).
-
-* Install `ejabberd` using the following command. Alternatively see .
-
- ```
- $ apt-get install ejabberd
- ```
-
-* Confirm that the ejabberd version you installed is the one mentioned above or later:
-
- ```
- $ ejabberdctl status
- ```
-
-* Configure ejabberd by setting the domain name of your choice and add an `admin` user.:
-
- ```
- $ dpkg-reconfigure ejabberd
- ````
-
-You should now be able to connect to this XMPP server using any XMPP client.
-
-### 1.2 Install python3 and SleekXmpp
-
-* The lobby bots are programmed in python3 and use SleekXMPP to connect to the lobby. Install these dependencies using:
-
- ```
- $ apt-get install python3 python3-sleekxmpp
- ```
-
-* Confirm that the SleekXmpp version is 1.3.1 or later:
-
- ```
- pip3 show sleekxmpp
- ```
-
-* If you would like to run the rating bot, you will need to install SQLAlchemy for python3:
-
- ```
- $ apt-get install python3-sqlalchemy
- ```
-
-## 2 (Optional) Install ejabberd ipstamp module
-
-### 2.1 Copy mod_ipstamp files.
-
-The ejabberd ipstamp module is used as a fallback for users without STUN capabilities.
-It inserts the IP to GameList "register" stanzas, which XpartMuPP sends back to the host.
-STUN-enabled users do not require it to host, so this is optional.
-
-* Adjust `/etc/ejabberd/ejabberdctl.cfg` and set `CONTRIB_MODULES_PATH` to the directory where you want to store `mod_ipstamp`:
-
- ```
- CONTRIB_MODULES_PATH=/opt/ejabberd-modules
- ```
-
-* Ensure the target directory is readable by ejabberd.
-* Copy the `mod_ipstamp` directory from `XpartaMuPP/` to `CONTRIB_MODULES_PATH/sources/`.
-* Check that the module is available and compatible with your ejabberd:
-
- ```
- $ ejabberdctl modules_available
- $ ejabberdctl module_check mod_ipstamp
- ```
-
-* Install `mod_ipstamp`:
-
- ```
- $ ejabberdctl module_install mod_ipstamp
- ```
-
-## 2.2. Configure ejabberd mod_ipstamp
-
-The ejabberd configuration in the remainder of this document is performed by editing `/etc/ejabberd/ejabberd.yml`.
-The directory containing this README includes a preconfigured `ejabberd_example.yml` that only needs few setting changes to work with your setup.
-For a full documentation of the ejabberd configuration, see .
-If something goes wrong with ejabberd, check `/var/log/ejabberd/ejabberd.log`
-
-* Add `mod_ipstamp` to the modules ejabberd should load:
-
- ```
- modules:
- mod_ipstamp: {}
- ```
-
-* Reload the ejabberd config.
- This should be done every few steps, so that configuration errors can be identified as soon as possible.
-
- ```
- $ ejabberdctl reload_config
- ```
-
-## 3. Configure ejabberd connectivity
-
-The settings in this section ensure that connections can be built where intended, and only where intended.
-
-### 3.1 Disable IPv6
-* Since the enet library which Pyrogenesis uses for multiplayer mode does not support IPv6, ejabberd must be configured to not use IPv6:
-
- ```
- listen:
- ip: "0.0.0.0"
- ```
-
-### 3.2 Enable STUN
-* ejabberd and Pyrogenesis support the STUN protocol. This allows players to connect to each others games even if the host did not configure the router and forward the UDP port.
-0 A.D. uses STUN to let hosts find their IP.
-
- ```
- listen:
- -
- port: 3478
- transport: udp
- module: ejabberd_stun
- ```
-
-### 3.3 Enable keep-alive
-
-* This helps with users becoming disconnected:
-
- ```
- modules:
- mod_ping:
- send_pings: true
- ```
-
-### 3.3 Disable unused services
-
-* Disable the currently unused server-to-server communication:
-
- ```
- listen:
- ## -
- ## port: 5269
- ## ip: "::"
- ## module: ejabberd_s2s_in
- ```
-
-* Protect the administrative webinterface at from external access by disabling or restriction to `localhost`:
-
- ```
- listen:
- -
- port: 5280
- ip: "127.0.0.1"
- ```
-
-* Disable some unused modules:
-
- ```
- modules:
- ## mod_echo: {}
- ## mod_irc: {}
- ## mod_shared_roster: {}
- ## mod_vcard: {}
- ## mod_vcard_xupdate: {}
- ```
-
-### 3.4 Setup TLS encryption
-
-Depending on whether you use the server for a player audience or only for local testing,
-you may have to either obtain and install a certificate with ejabberd or disable TLS encryption.
-
-#### Choice A: No encryption
-* If you intend to use the server solely for local testing, you may disable TLS encryption in the ejabberd config:
-
- ```
- listen:
- starttls_required: false
- ```
-
-#### Choice B: Self-signed certificate
-
-If you want to use the server for local testing only, you may use a self-signed certificate to test encryption.
-Notice the lobby bots currently reject self-signed certificates.
-
-* Enable TLS over the default port:
- ```
- listen:
- starttls: true
- ```
-
-* Create the key file for certificate:
-
- ```
- openssl genrsa -out key.pem 2048
- ```
-* Create the certificate file. “common name” should match the domainname.
-
- ```
- openssl req -new -key key.pem -out request.pem
- ```
-
-* Sign the certificate:
-
- ```
- openssl x509 -req -days 900 -in request.pem -signkey key.pem -out certificate.pem
- ```
-
-* Store it as the ejabberd certificate:
-
- ```
- $ cat key.pem request.pem > /etc/ejabberd/ejabberd.pem
- ```
-
-#### Choice C: Let's Encrypt certificate
-To secure user authentication and communication with modern encryption and to comply with privacy laws,
-ejabberd should be configured to use TLS with a proper, trusted certificate.
-
-* A free, valid, and trusted TLS certificate may be obtained from some certificate authorites, such as Let's Encrypt:
-
-
-
-* Enable TLS over the default port:
- ```
- listen:
- starttls: true
- ```
-
-* Setup the contact address if Let's Encrypt found an authentication issue:
-
- ```
- acme:
- contact: "mailto:admin@example.com"
- ```
-
-* Ensure old, vulnerable SSL/TLS protocols are disabled:
-
- ```
- define_macro:
- 'TLS_OPTIONS':
- - "no_sslv2"
- - "no_sslv3"
- - "no_tlsv1"
- ```
-
-## 3. Configure ejabberd use policy
-
-The settings in this section grant or restrict user access rights.
-
-* Prevent the rooms from being destroyed if the last client leaves it:
-
- ```
- access_rules:
- muc_admin:
- - allow: admin
- modules:
- mod_muc:
- access_persistent: muc_admin
- default_room_options:
- persistent: true
- ```
-
-* Allow users to create accounts using the game via in-band registration.
- ```
- access_rules:
- register:
- - all: allow
- ```
-
-### Optional use policies
-
-* (Optional) It is recommended to restrict usernames to alphanumeric characters (so that playernames are easily typeable for every participant).
- The username may be restricted in length (because very long usernames are uncomfortably time-consuming to read and may not fit into the playername fields).
- Notice the username regex below is also used by the 0 A.D. client to indicate invalid names to the user.
- ```
- acl:
- validname:
- user_regexp: "^[0-9A-Za-z._-]{1,20}$"
-
- access_rules:
- register:
- - allow: validname
-
- modules:
- mod_register:
- access: register
- ```
-
-* (Optional) Prevent users from creating new rooms:
-
- ```
- modules:
- mod_muc:
- access_create: muc_admin
- ```
-
-* (Optional) Increase the maximum number of users from the default 200:
-
- ```
- mod_muc:
- max_users: 5000
- default_room_options:
- max_users: 1000
- ```
-
-* (Optional) Prevent users from sending too large stanzas.
- Notice the bots can send large stanzas as well, so don't restrict it too much.
-
- ```
- max_stanza_size: 1048576
- ```
-
-
-* (Optional) Prevent users from changing the room topic:
-
- ```
- mod_muc:
- default_room_options:
- allow_change_subj: false
- ```
-
-* (Optional) Prevent malicious users from registering new accounts quickly if they were banned.
- Notice this also prevents players using the same internet router from registering for that time if they want to play together.
-
- ```
- registration_timeout: 3600
- ```
-
-* (Optional) Enable room chatlogging.
- Make sure to mention this collection and the purposes in the Terms and Conditions to comply with personal data laws.
- Ensure that ejabberd has write access to the given folder.
- Notice that `ejabberd.service` by default prevents write access to some directories (PrivateTmp, ProtectHome, ProtectSystem).
-
- ```
- modules:
- mod_muc_log:
- outdir: "/lobby/logs"
- file_format: plaintext
- timezone: universal
- mod_muc:
- default_room_options:
- logging: true
- ```
-
-* (Optional) Grant specific moderators administrator rights to see the IP address of a user:
- See also `https://xmpp.org/extensions/xep-0133.html#get-user-stats`.
-
- ```
- acl:
- admin:
- user:
- - "username@lobby.wildfiregames.com"
- ```
-
-* (Optional) Grant specific moderators to :
- See also `https://xmpp.org/extensions/xep-0133.html#get-user-stats`.
-
- ```
- modules:
- mod_muc:
- access_admin: muc_admin
- ```
-
-* (Optional) Ban specific IP addresses or subnet masks for persons that create new accounts after having been banned from the room:
-
- ```
- acl:
- blocked:
- ip:
- - "12.34.56.78"
- - "12.34.56.0/8"
- - "12.34.0.0/16"
- ...
- access_rules:
- c2s:
- - deny: blocked
- - allow
- register:
- - deny: blocked
- - allow
- ```
-
-## 4. Setup lobby bots
-
-### 4.1 Register lobby bot accounts
-
-* Check list of registered users:
-
- ```
- $ ejabberdctl registered_users lobby.wildfiregames.com
- ```
-
-* Register the accounts of the lobby bots.
- The rating account is only needed if you decided to enable the rating service.
-
- ```
- $ ejabberdctl register echelon23 lobby.wildfiregames.com secure_password
- $ ejabberdctl register xpartamupp23 lobby.wildfiregames.com secure_password
- ```
-
-### 4.2 Authorize lobby bots to see real JIDs
-
-* The bots need to be able to see real JIDs of users.
- So either the room must be configured as non-anonymous, i.e. real JIDs are visible to all users of the room,
- or the bots need to receive muc administrator rights.
-
-#### Choice A: Non-anonymous room
-* (Recommended) This method has the advantage that bots do not gain administrative access that they don't use.
- The only possible downside is that room users may not hide their username behind arbitrary nicknames anymore.
-
- ```
- modules:
- mod_muc:
- default_room_options:
- anonymous: false
- ```
-
-#### Choice B: Non-anonymous room
-* If you for any reason wish to configure the room as semi-anonymous (only muc administrators can see real JIDs),
- then the bots need to be authorized as muc administrators:
-
- ```
- access_rules:
- muc_admin:
- - allow: bots
-
- modules:
- mod_muc:
- access_admin: muc_admin
- ```
-
-### 4.3 Authorize lobby bots with ejabberd
-
-* The bots need an ACL to be able to get the IPs of users hosting a match (which is what `mod_ipstamp` does).
-
- ```
- acl:
- ## Don't use a regex, to prevent others from obtaining permissions after registering such an account.
- bots:
- - user: "xpartamupp23@lobby.wildfiregames.com"
- - user: "echelon23@lobby.wildfiregames.com"
- ```
-
-* Add an access rule for `ipbots` and a rule allowing bots to create PubSub nodes:
-
- ```
- access_rules:
- ## Expected by the ipstamp module for XpartaMuPP
- ipbots:
- - allow: bots
-
- pubsub_createnode:
- - allow: bots
- ```
-
-* Due to the amount of traffic the bot may process, give the group containing bots either unlimited or a very high traffic shaper:
-
- ```
- shaper_rules:
- c2s_shaper:
- - none: admin, bots
- - normal
- ```
-
-* Finally reload ejabberd's configuration:
-
- ```
- $ ejabberdctl reload_config
- ```
-
-### 4.4 Running XpartaMuPP - XMPP Multiplayer Game Manager
-
-* Execute the following command to run the gamelist bot:
-
- ```
- $ python3 XpartaMuPP.py --domain lobby.wildfiregames.com --login xpartamupp23 --password XXXXXX --nickname GamelistBot --room arena --elo echelon23
- ```
-
-If you want to run XpartaMuPP without a rating bot, the `--elo` argument should be omitted.
-Pass `--disable-tls` if you did not setup valid TLS encryption on the server.
-Run `python3 XpartaMuPP.py --help` for the full list of options
-
-* If the connection and authentication succeeded, you should see the following messages in the console:
-
- ```
- INFO JID set to: xpartamupp23@lobby.wildfiregames.com/CC
- INFO XpartaMuPP started
- ```
-
-### 4.5 Running EcheLOn - XMPP Multiplayer Rating Manager
-
-This bot can be thought of as a module of XpartaMuPP in that IQs stanzas sent to XpartaMuPP are
-forwarded onto EcheLOn if its corresponding EcheLOn is online and ignored otherwise.
-EcheLOn handles all aspects of operation related to ELO, the chess rating system invented by Arpad Elo.
-Players gain a rating after a rated 1v1 match.
-The score difference after a completed match is relative to the rating difference of the players.
-
-* (Optional) Some constants of the algorithm may be edited by experienced administrators at the head of `ELO.py`:
-
- ```
- # Difference between two ratings such that it is
- # regarded as a "sure win" for the higher player.
- # No points are gained or lost for such a game.
- elo_sure_win_difference = 600.0
-
- # Lower ratings "move faster" and change more
- # dramatically than higher ones. Anything rating above
- # this value moves at the same rate as this value.
- elo_k_factor_constant_rating = 2200.0
- ```
-
-* To initialize the `lobby_rankings.sqlite3` database, execute the following command:
-
- ```
- $ python3 LobbyRanking.py
- ```
-
-* Execute the following command to run the rating bot:
-
- ```
- $ python3 EcheLOn.py --domain lobby.wildfiregames.com --login echelon23 --password XXXXXX --nickname RatingBot --room arena23
- ```
-
-Run `python3 EcheLOn.py --help` for the full list of options
-
-## 5. Configure Pyrogenesis for the new Multiplayer Lobby
-
-The Pyrogenesis client is now going to be configured to become able to connect to the new Multiplayer Lobby.
-
-The Pyrogenesis documentation of configuration files can be found at .
-Available Pyrogenesis configuration settings are specified in `default.cfg`, see .
-
-### 5.1 Local Configuration
-
- * Visit to identify the local user's Pyrogenesis configuration path depending on the operating system.
-
- * Create or open `local.cfg` in the configuration path.
-
- * Add the following settings that determine the lobby server connection:
-
- ```
- lobby.room = "arena23" ; Default MUC room to join
- lobby.server = "lobby.wildfiregames.com" ; Address of lobby server
- lobby.stun.server = "lobby.wildfiregames.com" ; Address of the STUN server.
- lobby.require_tls = true ; Whether to reject connecting to the lobby if TLS encryption is unavailable.
- lobby.verify_certificate = true ; Whether to reject connecting to the lobby if the TLS certificate is invalid.
- lobby.xpartamupp = "xpartamupp23" ; Name of the server-side XMPP-account that manage games
- lobby.echelon = "echelon23" ; Name of the server-side XMPP-account that manages ratings
- ```
-
- If you disabled TLS encryption, set `require_tls` to `false`.
- If you employed a self-signed certificate, set `verify_certificate` to `false`.
-
-### 5.2 Test the Multiplayer Lobby
-
-You should now be able to join the new multiplayer lobby with the Pyrogenesis client and play multiplayer matches.
-
-* To confirm that the match hosting works as intended, create two user accounts, host a game with one, join the game with the other account.
-
-* To confirm that the rating service works as intended, resign a rated 1v1 match with two accounts.
-
-### 5.3 Terms and Conditions
-
-Players joining public servers are subject to Terms and Conditions of the service provider and subject to privacy laws such as GDPR.
-If you intend to use the server only for local testing, you may skip this step.
-
-* The following files should be created by the service provider:
-
- `Terms_of_Service.txt` to explain the service and the contract.
- `Terms_of_Use.txt` to explain what the user should and should not do.
- `Privacy_Policy.txt` to explain how personal data is handled.
-
-* To use Wildfire Games Terms as a template, obtain our Terms from a copy of the game or from or from
-
-
-* Replace all occurrences of `Wildfire Games` in the files with the one providing the new server.
-
-* Update the `Terms_of_Use.txt` depending on which behavior you would like to (not) see on your service.
-
-* Update the `Privacy_Policy.txt` depending on the user data processing in relation to the usage policies.
-Make sure to not violate privacy laws such as GDPR or COPPA while doing so.
-
-* The retention times of ejabberd logs are relevant to GDPR.
-Visit for details.
-
-* The terms should be published online, so users can save and print them.
- Add to your `local.cfg`:
-
- ```
- lobby.terms_url = "https://lobby.wildfiregames.com/terms/"; Allows the user to save the text and print the terms
- ```
-
-### 5.4 Distribute the configuration
-
-To make this a public server, distribute your `local.cfg`, `Terms_of_Service.txt`, `Terms_of_Use.txt`, `Privacy_Policy.txt`.
-
-It may be advisable to create a mod with a modified `default.cfg` and the new terms documents,
-see .
-
-Congratulations, you are now running a custom Pyrogenesis Multiplayer Lobby!
Property changes on: ps/trunk/source/tools/lobbybots/README.md
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/mod_ipstamp/mod_ipstamp.spec
===================================================================
--- ps/trunk/source/tools/lobbybots/mod_ipstamp/mod_ipstamp.spec (revision 27093)
+++ ps/trunk/source/tools/lobbybots/mod_ipstamp/mod_ipstamp.spec (nonexistent)
@@ -1,5 +0,0 @@
-author: "Wildfire Games"
-category: "log"
-summary: "Add senders IP address to game registration stanzas for 0ad"
-home: "undefined"
-url: "https://play0ad.com"
Property changes on: ps/trunk/source/tools/lobbybots/mod_ipstamp/mod_ipstamp.spec
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/mod_ipstamp/src/mod_ipstamp.erl
===================================================================
--- ps/trunk/source/tools/lobbybots/mod_ipstamp/src/mod_ipstamp.erl (revision 27093)
+++ ps/trunk/source/tools/lobbybots/mod_ipstamp/src/mod_ipstamp.erl (nonexistent)
@@ -1,72 +0,0 @@
-%% Copyright (C) 2018 Wildfire Games.
-%% This file is part of 0 A.D.
-%%
-%% 0 A.D. is free software: you can redistribute it and/or modify
-%% it under the terms of the GNU General Public License as published by
-%% the Free Software Foundation, either version 2 of the License, or
-%% (at your option) any later version.
-%%
-%% 0 A.D. is distributed in the hope that it will be useful,
-%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-%% GNU General Public License for more details.
-%%
-%% You should have received a copy of the GNU General Public License
-%% along with 0 A.D. If not, see .
-
--module(mod_ipstamp).
-
--behaviour(gen_mod).
-
--include("ejabberd.hrl").
--include("logger.hrl").
--include("xmpp.hrl").
-
--export([start/2,
- stop/1,
- depends/2,
- mod_opt_type/1,
- reload/3,
- on_filter_packet/1]).
-
-start(_Host, _Opts) ->
- ejabberd_hooks:add(filter_packet, global, ?MODULE, on_filter_packet, 50).
-
-stop(_Host) ->
- ejabberd_hooks:delete(filter_packet, global, ?MODULE, on_filter_packet, 50).
-
-depends(_Host, _Opts) -> [].
-
-mod_opt_type(_) -> [].
-
-reload(_Host, _NewOpts, _OldOpts) -> ok.
-
--spec on_filter_packet(Input :: iq()) -> iq() | drop.
-on_filter_packet(#iq{type = set, to = To, sub_els = [SubEl]} = Input) ->
- % We only want to do something for the bots
- case acl:match_rule(global, ipbots, To) of
- allow ->
- NS = xmpp:get_ns(SubEl),
- if NS == <<"jabber:iq:gamelist">> ->
- SCommand = fxml:get_path_s(SubEl, [{elem, <<"command">>}, cdata]),
- if SCommand == <<"register">> ->
- % Get the sender's IP.
- Ip = xmpp:get_meta(Input, ip),
- SIp = inet_parse:ntoa(Ip),
- ?INFO_MSG(string:concat("Inserting IP into game registration "
- "stanza: ", SIp), []),
- Game = fxml:get_subtag(SubEl, <<"game">>),
- GameWithIp = fxml:replace_tag_attr(<<"ip">>, SIp, Game),
- SubEl2 = fxml:replace_subtag(GameWithIp, SubEl),
- xmpp:set_els(Input, [SubEl2]);
- true ->
- Input
- end;
- true ->
- Input
- end;
- _ -> Input
- end;
-
-on_filter_packet(Input) ->
- Input.
Property changes on: ps/trunk/source/tools/lobbybots/mod_ipstamp/src/mod_ipstamp.erl
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/mod_ipstamp/COPYING
===================================================================
--- ps/trunk/source/tools/lobbybots/mod_ipstamp/COPYING (revision 27093)
+++ ps/trunk/source/tools/lobbybots/mod_ipstamp/COPYING (nonexistent)
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- , 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
Property changes on: ps/trunk/source/tools/lobbybots/mod_ipstamp/COPYING
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/mod_ipstamp/README.txt
===================================================================
--- ps/trunk/source/tools/lobbybots/mod_ipstamp/README.txt (revision 27093)
+++ ps/trunk/source/tools/lobbybots/mod_ipstamp/README.txt (nonexistent)
@@ -1,7 +0,0 @@
-mod_ipstamp
-===========
-
-mod_ipstamp is an ejabberd module for 0ad which adds ip addresses of a
-game host to game registration stanzas.
-
-For it to work the 0ad XMPP bots need to have the ACL "ipbots".
Property changes on: ps/trunk/source/tools/lobbybots/mod_ipstamp/README.txt
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/tools/lobbybots/xpartamupp/echelon.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/echelon.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/echelon.py (nonexistent)
@@ -1,803 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""0ad XMPP-bot responsible for managing game ratings."""
-
-import argparse
-import difflib
-import logging
-import sys
-from collections import deque
-
-import sleekxmpp
-from sleekxmpp.stanza import Iq
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
-from sqlalchemy import create_engine, func
-from sqlalchemy.orm import scoped_session, sessionmaker
-
-from xpartamupp.elo import get_rating_adjustment
-from xpartamupp.lobby_ranking import Game, Player, PlayerInfo
-from xpartamupp.stanzas import (BoardListXmppPlugin, GameReportXmppPlugin, ProfileXmppPlugin)
-from xpartamupp.utils import LimitedSizeDict
-
-# Rating that new players should be inserted into the
-# database with, before they've played any games.
-LEADERBOARD_DEFAULT_RATING = 1200
-
-
-class Leaderboard(object):
- """Class that provides and manages leaderboard data."""
-
- def __init__(self, db_url):
- """Initialize the leaderboard."""
- self.rating_messages = deque()
-
- engine = create_engine(db_url)
- session_factory = sessionmaker(bind=engine)
- self.db = scoped_session(session_factory)
-
- def get_or_create_player(self, jid):
- """Get a player from the leaderboard database.
-
- Get player information from the leaderboard database and
- create him first, if he doesn't exist yet.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player to get
-
- Returns:
- Player instance representing the player specified by the
- supplied JID
-
- """
- player = self.db.query(Player).filter(Player.jid.ilike(str(jid))).first()
- if player:
- return player
-
- player = Player(jid=str(jid), rating=-1)
- self.db.add(player)
- self.db.commit()
- logging.debug("Created player %s", jid)
- return player
-
- def get_profile(self, jid):
- """Get the leaderboard profile for the specified player.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player to retrieve the
- profile for
-
- Returns:
- dict with statistics about the requested player or None if
- the player isn't known
-
- """
- stats = {}
- player = self.db.query(Player).filter(Player.jid.ilike(str(jid))).first()
-
- if not player:
- logging.debug("Couldn't find profile for player %s", jid)
- return {}
-
- if player.rating != -1:
- stats['rating'] = player.rating
- rank = self.db.query(Player).filter(Player.rating >= player.rating).count()
- stats['rank'] = rank
-
- if player.highest_rating != -1:
- stats['highestRating'] = player.highest_rating
-
- games_played = self.db.query(PlayerInfo).filter_by(player_id=player.id).count()
- wins = self.db.query(Game).filter_by(winner_id=player.id).count()
- stats['totalGamesPlayed'] = games_played
- stats['wins'] = wins
- stats['losses'] = games_played - wins
- return stats
-
- def _add_game(self, game_report): # pylint: disable=too-many-locals
- """Add a game to the database.
-
- Add a game to the database and update the data on a
- player from game results.
-
- Arguments:
- game_report (dict): a report about a game
-
- Returns:
- Game object for the created game or None if the creation
- failed for any reason.
-
- """
- # Discard any games still in progress. We shouldn't get
- # reports from those games anyway.
- if 'active' in dict.values(game_report['playerStates']):
- logging.warning("Received a game report for an unfinished game")
- return None
-
- players = self.db.query(Player).filter(func.lower(Player.jid).in_(
- dict.keys(game_report['playerStates'])))
-
- winning_jid = [jid for jid, state in game_report['playerStates'].items()
- if state == 'won'][0]
-
- # single_stats = {'timeElapsed', 'mapName', 'teamsLocked', 'matchID'}
- total_score_stats = {'economyScore', 'militaryScore', 'totalScore'}
- resource_stats = {'foodGathered', 'foodUsed', 'woodGathered', 'woodUsed', 'stoneGathered',
- 'stoneUsed', 'metalGathered', 'metalUsed', 'vegetarianFoodGathered',
- 'treasuresCollected', 'lootCollected', 'tributesSent',
- 'tributesReceived'}
- units_stats = {'totalUnitsTrained', 'totalUnitsLost', 'enemytotalUnitsKilled',
- 'infantryUnitsTrained', 'infantryUnitsLost', 'enemyInfantryUnitsKilled',
- 'workerUnitsTrained', 'workerUnitsLost', 'enemyWorkerUnitsKilled',
- 'femaleCitizenUnitsTrained', 'femaleCitizenUnitsLost',
- 'enemyFemaleCitizenUnitsKilled', 'cavalryUnitsTrained', 'cavalryUnitsLost',
- 'enemyCavalryUnitsKilled', 'championUnitsTrained', 'championUnitsLost',
- 'enemyChampionUnitsKilled', 'heroUnitsTrained', 'heroUnitsLost',
- 'enemyHeroUnitsKilled', 'shipUnitsTrained', 'shipUnitsLost',
- 'enemyShipUnitsKilled', 'traderUnitsTrained', 'traderUnitsLost',
- 'enemyTraderUnitsKilled'}
- buildings_stats = {'totalBuildingsConstructed', 'totalBuildingsLost',
- 'enemytotalBuildingsDestroyed', 'civCentreBuildingsConstructed',
- 'civCentreBuildingsLost', 'enemyCivCentreBuildingsDestroyed',
- 'houseBuildingsConstructed', 'houseBuildingsLost',
- 'enemyHouseBuildingsDestroyed', 'economicBuildingsConstructed',
- 'economicBuildingsLost', 'enemyEconomicBuildingsDestroyed',
- 'outpostBuildingsConstructed', 'outpostBuildingsLost',
- 'enemyOutpostBuildingsDestroyed', 'militaryBuildingsConstructed',
- 'militaryBuildingsLost', 'enemyMilitaryBuildingsDestroyed',
- 'fortressBuildingsConstructed', 'fortressBuildingsLost',
- 'enemyFortressBuildingsDestroyed', 'wonderBuildingsConstructed',
- 'wonderBuildingsLost', 'enemyWonderBuildingsDestroyed'}
- market_stats = {'woodBought', 'foodBought', 'stoneBought', 'metalBought', 'tradeIncome'}
- misc_stats = {'civs', 'teams', 'percentMapExplored'}
-
- stats = total_score_stats | resource_stats | units_stats | buildings_stats | market_stats \
- | misc_stats
-
- player_infos = []
- for player in players:
- player_jid = sleekxmpp.jid.JID(player.jid)
- player_info = PlayerInfo(player=player)
- for report_name in stats:
- setattr(player_info, report_name, game_report[report_name][player_jid])
- player_infos.append(player_info)
-
- game = Game(map=game_report['mapName'], duration=int(game_report['timeElapsed']),
- teamsLocked=bool(game_report['teamsLocked']), matchID=game_report['matchID'])
- game.player_info.extend(player_infos)
- game.winner = self.db.query(Player).filter(Player.jid.ilike(str(winning_jid))).first()
- self.db.add(game)
- self.db.commit()
- return game
-
- @staticmethod
- def _verify_game(game_report):
- """Check whether or not the game should be rated.
-
- The criteria for rated games can be specified here.
-
- Arguments:
- game_report (dict): a report about a game
-
- Returns:
- True if the game should be rated, false otherwise.
-
- """
- winning_jids = [jid for jid, state in game_report['playerStates'].items()
- if state == 'won']
- # We only support 1v1s right now.
- if len(winning_jids) > 1 or len(dict.keys(game_report['playerStates'])) != 2:
- return False
- return True
-
- def _rate_game(self, game):
- """Update player ratings based on game outcome.
-
- Take a game with 2 players and alters their ratings based on
- the result of the game.
-
- Adjusts the players ratings in the database.
-
- Arguments:
- game (Game): game to rate
- """
- player1 = game.players[0]
- player2 = game.players[1]
- # Since it's impossible to draw in the game currently, the
- # database model, and therefore this code, requires a winner.
- # The Elo implementation does not, however.
- result = 1 if player1 == game.winner else -1
- # Player's ratings are -1 unless they have played a rated game.
- if player1.rating == -1:
- player1.rating = LEADERBOARD_DEFAULT_RATING
- if player2.rating == -1:
- player2.rating = LEADERBOARD_DEFAULT_RATING
-
- try:
- rating_adjustment1 = int(get_rating_adjustment(player1.rating, player2.rating,
- len(player1.games), len(player2.games),
- result))
- rating_adjustment2 = int(get_rating_adjustment(player2.rating, player1.rating,
- len(player2.games), len(player1.games),
- result * -1))
- except ValueError:
- rating_adjustment1 = 0
- rating_adjustment2 = 0
-
- if result == 1:
- result_qualitative = 'won'
- elif result == 0:
- result_qualitative = 'drew'
- else:
- result_qualitative = 'lost'
- name1 = sleekxmpp.jid.JID(player1.jid).local
- name2 = sleekxmpp.jid.JID(player2.jid).local
- self.rating_messages.append("A rated game has ended. %s %s against %s. Rating "
- "Adjustment: %s (%s -> %s) and %s (%s -> %s)." %
- (name1, result_qualitative, name2, name1, player1.rating,
- player1.rating + rating_adjustment1, name2, player2.rating,
- player2.rating + rating_adjustment2))
- player1.rating += rating_adjustment1
- player2.rating += rating_adjustment2
- if not player1.highest_rating:
- player1.highest_rating = -1
- if not player2.highest_rating:
- player2.highest_rating = -1
- player1.highest_rating = max(player1.rating, player1.highest_rating)
- player2.highest_rating = max(player2.rating, player2.highest_rating)
- self.db.commit()
-
- def get_rating_messages(self):
- """Get messages announcing rated games.
-
- Returns:
- list with the a messages about rated games
-
- """
- return self.rating_messages
-
- def add_and_rate_game(self, game_report):
- """Add and rate a game.
-
- If the game has only two players, rate the game.
-
- Arguments:
- game_report (dict): a report about a game
-
- Returns:
- Game object
-
- """
- game = self._add_game(game_report)
- if game and self._verify_game(game_report):
- self._rate_game(game)
- return game
-
- def get_board(self, limit=100):
- """Return the ratings of the highest ranked players.
-
- Arguments:
- limit (int): Number of players to return
-
- Returns:
- dict with player JIDs, nicks and ratings
-
- """
- ratings = {}
- players = self.db.query(Player).filter(Player.rating != -1) \
- .order_by(Player.rating.desc()).limit(limit)
- for player in players:
- ratings[player.jid] = {'name': sleekxmpp.jid.JID(player.jid).local,
- 'rating': player.rating}
- return ratings
-
- def get_rating_list(self, nicks):
- """Return the ratings of all online players.
-
- The returned dictionary is by nick because the client can't
- link JID to nick conveniently.
-
- Arguments:
- nicks (dict): Players currently online
-
- Returns:
- dict with player JIDs, nicks and ratings
-
- """
- ratings = {}
- if nicks:
- player_filter = func.lower(Player.jid).in_([str(jid).lower() for jid in list(nicks)])
- players = self.db.query(Player.jid, Player.rating).filter(player_filter)
- for player in players:
- rating = str(player.rating) if player.rating != -1 else ''
- for jid in list(nicks):
- if jid == sleekxmpp.jid.JID(player.jid):
- ratings[nicks[str(jid)]] = {'name': nicks[jid], 'rating': rating}
- break
- return ratings
-
-
-class ReportManager(object):
- """Class which manages different game reports from clients.
-
- Calls leaderboard functions as appropriate.
- """
-
- def __init__(self, leaderboard):
- """Initialize the report manager.
-
- Arguments:
- leaderboard (Leaderboard): Leaderboard the manager is for
-
- """
- self.leaderboard = leaderboard
- self.interim_report_tracker = LimitedSizeDict(size_limit=2**12)
-
- def add_report(self, jid, raw_game_report):
- """Add a game to the interface between a raw report and the leaderboard database.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player who submitted
- the report
- raw_game_report (dict): Game report generated by 0ad
-
- """
- player_index = int(raw_game_report['playerID']) - 1
- del raw_game_report['playerID']
- match_id = raw_game_report['matchID']
- if match_id not in self.interim_report_tracker:
- self.interim_report_tracker[match_id] = {
- 'report': raw_game_report,
- 'jids': {player_index: str(jid)}
- }
- else:
- current_match = self.interim_report_tracker[match_id]
- if raw_game_report != current_match['report']:
- report_diff = self._get_report_diff(raw_game_report, current_match['report'])
- logging.warning("Retrieved reports for match %s differ:\n %s", match_id,
- report_diff)
- return
-
- player_jids = current_match['jids']
- if player_index in player_jids:
- if player_jids[player_index] == jid:
- logging.warning("Received a report for match %s from player %s twice.",
- match_id, jid)
- else:
- logging.warning("Retrieved a report for match %s for the same player twice, "
- "but from two different XMPP accounts: %s vs. %s", match_id,
- player_jids[player_index], jid)
- return
- else:
- player_jids[player_index] = str(jid)
-
- num_players = self._get_num_players(raw_game_report)
- num_retrieved_reports = len(player_jids)
- if num_retrieved_reports == num_players:
- try:
- self.leaderboard.add_and_rate_game(self._expand_report(
- current_match))
- except Exception:
- logging.exception("Failed to add and rate a game.")
- del current_match
- elif num_retrieved_reports < num_players:
- logging.warning("Haven't received all reports for the game yet. %i/%i",
- num_retrieved_reports, num_players)
- elif num_retrieved_reports > num_players:
- logging.warning("Retrieved more reports than players. This shouldn't happen.")
-
- @staticmethod
- def _expand_report(game_report):
- """Re-formats a game report into Python data structures.
-
- Player specific values from the report are replaced with a
- dict where the JID of the player is the key.
-
- Arguments:
- game_report (dict): wrapped game report from 0ad
-
- Returns a processed gameReport of type dict.
- """
- processed_game_report = {}
- for key, value in game_report['report'].items():
- if ',' not in value:
- processed_game_report[key] = value
- else:
- stat_to_jid = {}
- for i, part in enumerate(game_report['report'][key].split(",")[:-1]):
- stat_to_jid[game_report['jids'][i]] = part
- processed_game_report[key] = stat_to_jid
- return processed_game_report
-
- @staticmethod
- def _get_num_players(raw_game_report):
- """Compute the number of players from a raw game report.
-
- Get the number of players who played a game from the
- playerStates field in a raw game report.
-
- Arguments:
- raw_game_report (dict): Game report generated by 0ad
-
- Returns:
- int with the number of players in the game
-
- Raises:
- ValueError if the number of players couldn't be determined
-
- """
- if 'playerStates' in raw_game_report and ',' in raw_game_report['playerStates']:
- return len(list(filter(None, raw_game_report['playerStates'].split(","))))
- raise ValueError()
-
- @staticmethod
- def _get_report_diff(report1, report2):
- """Get differences between two reports.
-
- Arguments:
- report1 (dict): Game report
- report2 (dict): Game report
-
- Returns:
- str with a textual representation of the differences
- between the two reports
-
- """
- report1_list = ['{ %s: %s }' % (key, value) for key, value in report1.items()]
- report2_list = ['{ %s: %s }' % (key, value) for key, value in report2.items()]
- return '\n'.join(difflib.ndiff(report1_list, report2_list))
-
-
-class EcheLOn(sleekxmpp.ClientXMPP):
- """Main class which handles IQ data and sends new data."""
-
- def __init__(self, sjid, password, room, nick, leaderboard):
- """Initialize EcheLOn."""
- sleekxmpp.ClientXMPP.__init__(self, sjid, password)
- self.whitespace_keepalive = False
-
- self.sjid = sleekxmpp.jid.JID(sjid)
- self.room = room
- self.nick = nick
-
- self.leaderboard = leaderboard
- self.report_manager = ReportManager(self.leaderboard)
-
- register_stanza_plugin(Iq, BoardListXmppPlugin)
- register_stanza_plugin(Iq, GameReportXmppPlugin)
- register_stanza_plugin(Iq, ProfileXmppPlugin)
-
- self.register_handler(Callback('Iq Boardlist', StanzaPath('iq@type=get/boardlist'),
- self._iq_board_list_handler))
- self.register_handler(Callback('Iq GameReport', StanzaPath('iq@type=set/gamereport'),
- self._iq_game_report_handler))
- self.register_handler(Callback('Iq Profile', StanzaPath('iq@type=get/profile'),
- self._iq_profile_handler))
-
- self.add_event_handler('session_start', self._session_start)
- self.add_event_handler('muc::%s::got_online' % self.room, self._muc_online)
- self.add_event_handler('muc::%s::got_offline' % self.room, self._muc_offline)
- self.add_event_handler('groupchat_message', self._muc_message)
-
- def _session_start(self, event): # pylint: disable=unused-argument
- """Join MUC channel and announce presence.
-
- Arguments:
- event (dict): empty dummy dict
-
- """
- self.plugin['xep_0045'].joinMUC(self.room, self.nick)
- self.send_presence()
- self.get_roster()
- logging.info("EcheLOn started")
-
- def _muc_online(self, presence):
- """Add joining players to the list of players.
-
- Arguments:
- presence (sleekxmpp.stanza.presence.Presence): Received
- presence stanza.
-
- """
- nick = str(presence['muc']['nick'])
- jid = sleekxmpp.jid.JID(presence['muc']['jid'])
-
- if nick == self.nick:
- return
-
- if jid.resource != '0ad':
- return
-
- self.leaderboard.get_or_create_player(jid)
-
- self._broadcast_rating_list()
-
- logging.debug("Client '%s' connected with a nick of '%s'.", jid, nick)
-
- def _muc_offline(self, presence):
- """Remove leaving players from the list of players.
-
- Arguments:
- presence (sleekxmpp.stanza.presence.Presence): Received
- presence stanza.
-
- """
- nick = str(presence['muc']['nick'])
- jid = sleekxmpp.jid.JID(presence['muc']['jid'])
-
- if nick == self.nick:
- return
-
- logging.debug("Client '%s' with nick '%s' disconnected", jid, nick)
-
- def _muc_message(self, msg):
- """Process messages in the MUC room.
-
- Respond to messages highlighting the bots name with an
- informative message.
-
- Arguments:
- msg (sleekxmpp.stanza.message.Message): Received MUC
- message
- """
- if msg['mucnick'] != self.nick and self.nick.lower() in msg['body'].lower():
- self.send_message(mto=msg['from'].bare,
- mbody="I am just a bot and provide the rating functionality for "
- "this lobby. Please don't disturb me, calculating these "
- "ratings is already difficult enough.",
- mtype='groupchat')
-
- def _iq_board_list_handler(self, iq):
- """Handle incoming leaderboard list requests.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): Received IQ stanza
-
- """
- if iq['from'].resource not in ['0ad']:
- return
-
- command = iq['boardlist']['command']
- self.leaderboard.get_or_create_player(iq['from'])
- if command == 'getleaderboard':
- try:
- self._send_leaderboard(iq)
- except Exception:
- logging.exception("Failed to process get leaderboard request from %s",
- iq['from'].bare)
- elif command == 'getratinglist':
- try:
- self._send_rating_list(iq)
- except Exception:
- logging.exception("Failed to send the rating list to %s", iq['from'])
-
- def _iq_game_report_handler(self, iq):
- """Handle end of game reports from clients.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): Received IQ stanza
-
- """
- if iq['from'].resource not in ['0ad']:
- return
-
- try:
- self.report_manager.add_report(iq['from'], iq['gamereport']['game'])
- except Exception:
- logging.exception("Failed to update game statistics for %s", iq['from'].bare)
-
- rating_messages = self.leaderboard.get_rating_messages()
- if rating_messages:
- while rating_messages:
- message = rating_messages.popleft()
- self.send_message(mto=self.room, mbody=message, mtype='groupchat', mnick=self.nick)
- self._broadcast_rating_list()
-
- def _iq_profile_handler(self, iq):
- """Handle profile requests from clients.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): Received IQ stanza
-
- """
- if iq['from'].resource not in ['0ad']:
- return
-
- try:
- self._send_profile(iq, iq['profile']['command'])
- except Exception:
- logging.exception("Failed to send profile about %s to %s", iq['profile']['command'],
- iq['from'].bare)
-
- def _send_leaderboard(self, iq):
- """Send the whole leaderboard.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): IQ stanza to reply to
-
- """
- ratings = self.leaderboard.get_board()
-
- iq = iq.reply(clear=True)
- stanza = BoardListXmppPlugin()
- stanza.add_command('boardlist')
- for player in ratings.values():
- stanza.add_item(player['name'], player['rating'])
- iq.set_payload(stanza)
-
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send leaderboard to %s", iq['to'])
-
- def _send_rating_list(self, iq):
- """Send the ratings of all online players.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): IQ stanza to reply to
-
- """
- nicks = {}
- for nick in self.plugin['xep_0045'].getRoster(self.room):
- if nick == self.nick:
- continue
- jid_str = self.plugin['xep_0045'].getJidProperty(self.room, nick, 'jid')
- jid = sleekxmpp.jid.JID(jid_str)
- nicks[jid] = nick
- ratings = self.leaderboard.get_rating_list(nicks)
-
- iq = iq.reply(clear=True)
- stanza = BoardListXmppPlugin()
- stanza.add_command('ratinglist')
- for player in ratings.values():
- stanza.add_item(player['name'], player['rating'])
- iq.set_payload(stanza)
-
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send rating list to %s", iq['to'])
-
- def _broadcast_rating_list(self):
- """Broadcast the ratings of all online players."""
- nicks = {}
- for nick in self.plugin['xep_0045'].getRoster(self.room):
- if nick == self.nick:
- continue
- jid_str = self.plugin['xep_0045'].getJidProperty(self.room, nick, 'jid')
- jid = sleekxmpp.jid.JID(jid_str)
- nicks[jid] = nick
- ratings = self.leaderboard.get_rating_list(nicks)
-
- stanza = BoardListXmppPlugin()
- stanza.add_command('ratinglist')
- for player in ratings.values():
- stanza.add_item(player['name'], player['rating'])
-
- for jid in nicks:
- iq = self.make_iq_result(ito=jid)
- iq.set_payload(stanza)
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send rating list to %s", jid)
-
- def _send_profile(self, iq, player_nick):
- """Send the player profile to a specified target.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): IQ stanza to reply to
- player_nick (str): The nick of the player to get the
- profile for
-
- """
- jid_str = self.plugin['xep_0045'].getJidProperty(self.room, player_nick, 'jid')
- player_jid = sleekxmpp.jid.JID(jid_str) if jid_str else None
-
- # The player the profile got requested for is not online, so
- # let's assume the JID contains the nick as local part.
- if not player_jid:
- player_jid = sleekxmpp.jid.JID('%s@%s/%s' % (player_nick, self.sjid.domain, '0ad'))
-
- try:
- stats = self.leaderboard.get_profile(player_jid)
- except Exception:
- logging.exception("Failed to get leaderboard profile for player %s", player_jid)
- stats = {}
-
- iq = iq.reply(clear=True)
- stanza = ProfileXmppPlugin()
- if stats:
- stanza.add_item(player_nick, stats['rating'], stats['highestRating'],
- stats['rank'], stats['totalGamesPlayed'], stats['wins'],
- stats['losses'])
- else:
- stanza.add_item(player_nick, -2)
- stanza.add_command(player_nick)
- iq.set_payload(stanza)
-
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send profile to %s", iq['to'])
-
-
-def parse_args(args):
- """Parse command line arguments.
-
- Arguments:
- args (dict): Raw command line arguments given to the script
-
- Returns:
- Parsed command line arguments
-
- """
- parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- description="EcheLOn - XMPP Rating Bot")
-
- log_settings = parser.add_mutually_exclusive_group()
- log_settings.add_argument('-q', '--quiet', help="only log errors", action='store_const',
- dest='log_level', const=logging.ERROR)
- log_settings.add_argument('-d', '--debug', help="log debug messages", action='store_const',
- dest='log_level', const=logging.DEBUG)
- log_settings.add_argument('-v', '--verbose', help="log more informative messages",
- action='store_const', dest='log_level', const=logging.INFO)
- log_settings.set_defaults(log_level=logging.WARNING)
-
- parser.add_argument('-m', '--domain', help="XMPP server to connect to",
- default='lobby.wildfiregames.com')
- parser.add_argument('-l', '--login', help="username for login", default='EcheLOn')
- parser.add_argument('-p', '--password', help="password for login", default='XXXXXX')
- parser.add_argument('-n', '--nickname', help="nickname shown to players", default='RatingsBot')
- parser.add_argument('-r', '--room', help="XMPP MUC room to join", default='arena')
- parser.add_argument('--database-url', help="URL for the leaderboard database",
- default='sqlite:///lobby_rankings.sqlite3')
- parser.add_argument('-s', '--server', help='address of the ejabberd server',
- action='store', dest='xserver', default=None)
- parser.add_argument('-t', '--disable-tls', help='Pass this argument to connect without TLS encryption',
- action='store_true', dest='xdisabletls', default=False)
-
- return parser.parse_args(args)
-
-def main():
- """Entry point a console script."""
- args = parse_args(sys.argv[1:])
-
- logging.basicConfig(level=args.log_level,
- format='%(asctime)s %(levelname)-8s %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S')
-
- leaderboard = Leaderboard(args.database_url)
- xmpp = EcheLOn(sleekxmpp.jid.JID('%s@%s/%s' % (args.login, args.domain, 'CC')), args.password,
- args.room + '@conference.' + args.domain, args.nickname, leaderboard)
- xmpp.register_plugin('xep_0030') # Service Discovery
- xmpp.register_plugin('xep_0004') # Data Forms
- xmpp.register_plugin('xep_0045') # Multi-User Chat
- xmpp.register_plugin('xep_0060') # Publish-Subscribe
- xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
-
- if xmpp.connect((args.xserver, 5222) if args.xserver else None, True, not args.xdisabletls):
- xmpp.process()
- else:
- logging.error("Unable to connect")
-
-
-if __name__ == '__main__':
- main()
Index: ps/trunk/source/tools/lobbybots/xpartamupp/__init__.py
===================================================================
Index: ps/trunk/source/tools/lobbybots/xpartamupp/elo.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/elo.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/elo.py (nonexistent)
@@ -1,82 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""Implementation of the ELO-rating algorithm for 0ad games."""
-
-# Difference between two ratings such that it is regarded as a "sure
-# win" for the higher player. No points are gained or lost for such a
-# game.
-ELO_SURE_WIN_DIFFERENCE = 600
-
-# Lower ratings "move faster" and change more
-# dramatically than higher ones. Anything rating above
-# this value moves at the same rate as this value.
-ELO_K_FACTOR_CONSTANT_RATING = 2200
-
-# This preset number of games is the number of games where a player is
-# considered "stable". Rating volatility is constant after this number.
-VOLATILITY_CONSTANT = 20
-
-# Fair rating adjustment loses against inflation.
-# This constant will battle inflation.
-# NOTE: This can be adjusted as needed by a bot/server administrator
-ANTI_INFLATION = 0.015
-
-
-def get_rating_adjustment(rating, opponent_rating, games_played,
- opponent_games_played, result): # pylint: disable=unused-argument
- """Calculate the rating adjustment after rated 1v1 games.
-
- The rating adjustment is calculated using a simplified
- ELO-algorithm.
-
- The given implementation doesn't work for negative ratings below
- -2199. This is a known limitation which is currently considered
- to be not relevant in day-to-day use.
-
- Arguments:
- rating (int): Rating of the first player before the game.
- opponent_rating (int): Rating of the second player before the
- game.
- games_played (int): Number of games the first player has played
- before this game.
- opponent_games_played (int): Number of games the second player
- has played before this game.
- result (int): 1 if the first player won, 0 if draw or -1 if the
- second player won.
-
- Returns:
- int: the adjustment which should be applied to the rating of
- the first player
-
- """
- if rating < -2199 or opponent_rating < -2199:
- raise ValueError('Too small rating given: rating: %i, opponent rating: %i' %
- (rating, opponent_rating))
-
- rating_k_factor = 50.0 * (min(rating, ELO_K_FACTOR_CONSTANT_RATING) /
- ELO_K_FACTOR_CONSTANT_RATING + 1.0) / 2.0
- player_volatility = (min(max(0, games_played), VOLATILITY_CONSTANT) /
- VOLATILITY_CONSTANT + 0.25) / 1.25
- volatility = rating_k_factor * player_volatility
- rating_difference = opponent_rating - rating
- rating_adjustment = (rating_difference + result * ELO_SURE_WIN_DIFFERENCE) / volatility - \
- ANTI_INFLATION
- if result == 1:
- return round(max(0.0, rating_adjustment))
- elif result == -1:
- return round(min(0.0, rating_adjustment))
- return round(rating_adjustment)
Index: ps/trunk/source/tools/lobbybots/xpartamupp/lobby_ranking.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/lobby_ranking.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/lobby_ranking.py (nonexistent)
@@ -1,175 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""Database schema used by the XMPP bots to store game information."""
-
-import argparse
-import sys
-
-from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, create_engine
-from sqlalchemy.orm import relationship
-from sqlalchemy.ext.declarative import declarative_base
-
-Base = declarative_base()
-
-
-class Player(Base):
- """Model representing players."""
-
- __tablename__ = 'players'
-
- id = Column(Integer, primary_key=True)
- jid = Column(String(255))
- rating = Column(Integer)
- highest_rating = Column(Integer)
- games = relationship('Game', secondary='players_info')
- # These two relations really only exist to satisfy the linkage
- # between PlayerInfo and Player and Game and player.
- games_info = relationship('PlayerInfo', backref='player')
- games_won = relationship('Game', backref='winner')
-
-
-class PlayerInfo(Base):
- """Model representing game results."""
-
- __tablename__ = 'players_info'
-
- id = Column(Integer, primary_key=True)
- player_id = Column(Integer, ForeignKey('players.id'))
- game_id = Column(Integer, ForeignKey('games.id'))
- civs = Column(String(20))
- teams = Column(Integer)
- economyScore = Column(Integer)
- militaryScore = Column(Integer)
- totalScore = Column(Integer)
- foodGathered = Column(Integer)
- foodUsed = Column(Integer)
- woodGathered = Column(Integer)
- woodUsed = Column(Integer)
- stoneGathered = Column(Integer)
- stoneUsed = Column(Integer)
- metalGathered = Column(Integer)
- metalUsed = Column(Integer)
- vegetarianFoodGathered = Column(Integer)
- treasuresCollected = Column(Integer)
- lootCollected = Column(Integer)
- tributesSent = Column(Integer)
- tributesReceived = Column(Integer)
- totalUnitsTrained = Column(Integer)
- totalUnitsLost = Column(Integer)
- enemytotalUnitsKilled = Column(Integer)
- infantryUnitsTrained = Column(Integer)
- infantryUnitsLost = Column(Integer)
- enemyInfantryUnitsKilled = Column(Integer)
- workerUnitsTrained = Column(Integer)
- workerUnitsLost = Column(Integer)
- enemyWorkerUnitsKilled = Column(Integer)
- femaleCitizenUnitsTrained = Column(Integer)
- femaleCitizenUnitsLost = Column(Integer)
- enemyFemaleCitizenUnitsKilled = Column(Integer)
- cavalryUnitsTrained = Column(Integer)
- cavalryUnitsLost = Column(Integer)
- enemyCavalryUnitsKilled = Column(Integer)
- championUnitsTrained = Column(Integer)
- championUnitsLost = Column(Integer)
- enemyChampionUnitsKilled = Column(Integer)
- heroUnitsTrained = Column(Integer)
- heroUnitsLost = Column(Integer)
- enemyHeroUnitsKilled = Column(Integer)
- shipUnitsTrained = Column(Integer)
- shipUnitsLost = Column(Integer)
- enemyShipUnitsKilled = Column(Integer)
- traderUnitsTrained = Column(Integer)
- traderUnitsLost = Column(Integer)
- enemyTraderUnitsKilled = Column(Integer)
- totalBuildingsConstructed = Column(Integer)
- totalBuildingsLost = Column(Integer)
- enemytotalBuildingsDestroyed = Column(Integer)
- civCentreBuildingsConstructed = Column(Integer)
- civCentreBuildingsLost = Column(Integer)
- enemyCivCentreBuildingsDestroyed = Column(Integer)
- houseBuildingsConstructed = Column(Integer)
- houseBuildingsLost = Column(Integer)
- enemyHouseBuildingsDestroyed = Column(Integer)
- economicBuildingsConstructed = Column(Integer)
- economicBuildingsLost = Column(Integer)
- enemyEconomicBuildingsDestroyed = Column(Integer)
- outpostBuildingsConstructed = Column(Integer)
- outpostBuildingsLost = Column(Integer)
- enemyOutpostBuildingsDestroyed = Column(Integer)
- militaryBuildingsConstructed = Column(Integer)
- militaryBuildingsLost = Column(Integer)
- enemyMilitaryBuildingsDestroyed = Column(Integer)
- fortressBuildingsConstructed = Column(Integer)
- fortressBuildingsLost = Column(Integer)
- enemyFortressBuildingsDestroyed = Column(Integer)
- wonderBuildingsConstructed = Column(Integer)
- wonderBuildingsLost = Column(Integer)
- enemyWonderBuildingsDestroyed = Column(Integer)
- woodBought = Column(Integer)
- foodBought = Column(Integer)
- stoneBought = Column(Integer)
- metalBought = Column(Integer)
- tradeIncome = Column(Integer)
- percentMapExplored = Column(Integer)
-
-
-class Game(Base):
- """Model representing games."""
-
- __tablename__ = 'games'
-
- id = Column(Integer, primary_key=True)
- map = Column(String(80))
- duration = Column(Integer)
- teamsLocked = Column(Boolean)
- matchID = Column(String(20))
- winner_id = Column(Integer, ForeignKey('players.id'))
- player_info = relationship('PlayerInfo', backref='game')
- players = relationship('Player', secondary='players_info')
-
-
-def parse_args(args):
- """Parse command line arguments.
-
- Arguments:
- args (dict): Raw command line arguments given to the script
-
- Returns:
- Parsed command line arguments
-
- """
- parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- description="Helper command for database creation")
- parser.add_argument('action', help='Action to apply to the database',
- choices=['create'])
- parser.add_argument('--database-url', help='URL for the leaderboard database',
- default='sqlite:///lobby_rankings.sqlite3')
- return parser.parse_args(args)
-
-
-def main():
- """Entry point a console script."""
- args = parse_args(sys.argv[1:])
- engine = create_engine(args.database_url)
- if args.action == 'create':
- Base.metadata.create_all(engine)
-
-
-if __name__ == '__main__':
- main()
Index: ps/trunk/source/tools/lobbybots/xpartamupp/utils.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/utils.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/utils.py (nonexistent)
@@ -1,48 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""Collection of utility functions used by the XMPP-bots."""
-
-from collections import OrderedDict
-
-
-class LimitedSizeDict(OrderedDict):
- """Dictionary with limited size and FIFO characteristics."""
-
- def __init__(self, *args, **kwargs):
- """Initialize the dictionary.
-
- Set the limit to which size the dict should be able to grow.
- """
- self.size_limit = kwargs.pop('size_limit', None)
- OrderedDict.__init__(self, *args, **kwargs)
- self._check_size_limit()
-
- def __setitem__(self, key, value): # pylint: disable=signature-differs
- """Overwrite default method to add size limit check."""
- OrderedDict.__setitem__(self, key, value)
- self._check_size_limit()
-
- def _check_size_limit(self):
- """Ensure dict is not larger than the size limit.
-
- Compares the current size of the dict with the size limit and
- removes items from the dict until the size is equal the size
- limit.
- """
- if self.size_limit:
- while len(self) > self.size_limit:
- self.popitem(last=False)
Index: ps/trunk/source/tools/lobbybots/xpartamupp/xpartamupp.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/xpartamupp.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/xpartamupp.py (nonexistent)
@@ -1,364 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""0ad XMPP-bot responsible for managing game listings."""
-
-import argparse
-import logging
-import time
-import sys
-
-import sleekxmpp
-from sleekxmpp.stanza import Iq
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
-
-from xpartamupp.stanzas import GameListXmppPlugin
-from xpartamupp.utils import LimitedSizeDict
-
-
-class Games(object):
- """Class to tracks all games in the lobby."""
-
- def __init__(self):
- """Initialize with empty games."""
- self.games = LimitedSizeDict(size_limit=2**7)
-
- def add_game(self, jid, data):
- """Add a game.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player who started the
- game
- data (dict): information about the game
-
- Returns:
- True if adding the game succeeded, False if not
-
- """
- try:
- data['players-init'] = data['players']
- data['nbp-init'] = data['nbp']
- data['state'] = 'init'
- except (KeyError, TypeError, ValueError):
- logging.warning("Received invalid data for add game from 0ad: %s", data)
- return False
- else:
- self.games[jid] = data
- return True
-
- def remove_game(self, jid):
- """Remove a game attached to a JID.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player whose game to
- remove.
-
- Returns:
- True if removing the game succeeded, False if not
-
- """
- try:
- del self.games[jid]
- except KeyError:
- logging.warning("Game for jid %s didn't exist", jid)
- return False
- else:
- return True
-
- def get_all_games(self):
- """Return all games.
-
- Returns:
- dict containing all games with the JID of the player who
- started the game as key.
-
- """
- return self.games
-
- def change_game_state(self, jid, data):
- """Switch game state between running and waiting.
-
- Arguments:
- jid (sleekxmpp.jid.JID): JID of the player whose game to
- change
- data (dict): information about the game
-
- Returns:
- True if changing the game state succeeded, False if not
-
- """
- if jid not in self.games:
- logging.warning("Tried to change state for non-existent game %s", jid)
- return False
-
- try:
- if self.games[jid]['nbp-init'] > data['nbp']:
- logging.debug("change game (%s) state from %s to %s", jid,
- self.games[jid]['state'], 'waiting')
- self.games[jid]['state'] = 'waiting'
- else:
- logging.debug("change game (%s) state from %s to %s", jid,
- self.games[jid]['state'], 'running')
- self.games[jid]['state'] = 'running'
- self.games[jid]['nbp'] = data['nbp']
- self.games[jid]['players'] = data['players']
- except (KeyError, ValueError):
- logging.warning("Received invalid data for change game state from 0ad: %s", data)
- return False
- else:
- if 'startTime' not in self.games[jid]:
- self.games[jid]['startTime'] = str(round(time.time()))
- return True
-
-
-class XpartaMuPP(sleekxmpp.ClientXMPP):
- """Main class which handles IQ data and sends new data."""
-
- def __init__(self, sjid, password, room, nick):
- """Initialize XpartaMuPP.
-
- Arguments:
- sjid (sleekxmpp.jid.JID): JID to use for authentication
- password (str): password to use for authentication
- room (str): XMPP MUC room to join
- nick (str): Nick to use in MUC
-
- """
- sleekxmpp.ClientXMPP.__init__(self, sjid, password)
- self.whitespace_keepalive = False
-
- self.room = room
- self.nick = nick
-
- self.games = Games()
-
- register_stanza_plugin(Iq, GameListXmppPlugin)
-
- self.register_handler(Callback('Iq Gamelist', StanzaPath('iq@type=set/gamelist'),
- self._iq_game_list_handler))
-
- self.add_event_handler('session_start', self._session_start)
- self.add_event_handler('muc::%s::got_online' % self.room, self._muc_online)
- self.add_event_handler('muc::%s::got_offline' % self.room, self._muc_offline)
- self.add_event_handler('groupchat_message', self._muc_message)
-
- def _session_start(self, event): # pylint: disable=unused-argument
- """Join MUC channel and announce presence.
-
- Arguments:
- event (dict): empty dummy dict
-
- """
- self.plugin['xep_0045'].joinMUC(self.room, self.nick)
- self.send_presence()
- self.get_roster()
- logging.info("XpartaMuPP started")
-
- def _muc_online(self, presence):
- """Add joining players to the list of players.
-
- Also send a list of games to them, so they see which games
- are currently there.
-
- Arguments:
- presence (sleekxmpp.stanza.presence.Presence): Received
- presence stanza.
-
- """
- nick = str(presence['muc']['nick'])
- jid = sleekxmpp.jid.JID(presence['muc']['jid'])
-
- if nick == self.nick:
- return
-
- if jid.resource not in ['0ad', 'CC']:
- return
-
- self._send_game_list(jid)
-
- logging.debug("Client '%s' connected with a nick '%s'.", jid, nick)
-
- def _muc_offline(self, presence):
- """Remove leaving players from the list of players.
-
- Also remove the potential game this player was hosting, so we
- don't end up with stale games.
-
- Arguments:
- presence (sleekxmpp.stanza.presence.Presence): Received
- presence stanza.
-
- """
- nick = str(presence['muc']['nick'])
- jid = sleekxmpp.jid.JID(presence['muc']['jid'])
-
- if nick == self.nick:
- return
-
- if self.games.remove_game(jid):
- self._send_game_list()
-
- logging.debug("Client '%s' with nick '%s' disconnected", jid, nick)
-
- def _muc_message(self, msg):
- """Process messages in the MUC room.
-
- Respond to messages highlighting the bots name with an
- informative message.
-
- Arguments:
- msg (sleekxmpp.stanza.message.Message): Received MUC
- message
- """
- if msg['mucnick'] != self.nick and self.nick.lower() in msg['body'].lower():
- self.send_message(mto=msg['from'].bare,
- mbody="I am just a bot and I'm responsible to ensure that your're"
- "able to see the list of games in here. Aside from that I'm"
- "just chilling.",
- mtype='groupchat')
-
- def _iq_game_list_handler(self, iq):
- """Handle game state change requests.
-
- Arguments:
- iq (sleekxmpp.stanza.iq.IQ): Received IQ stanza
-
- """
- if iq['from'].resource != '0ad':
- return
-
- success = False
-
- command = iq['gamelist']['command']
- if command == 'register':
- success = self.games.add_game(iq['from'], iq['gamelist']['game'])
- elif command == 'unregister':
- success = self.games.remove_game(iq['from'])
- elif command == 'changestate':
- success = self.games.change_game_state(iq['from'], iq['gamelist']['game'])
- else:
- logging.info('Received unknown game command: "%s"', command)
-
- iq.reply(clear=not success)
- if not success: iq['error']['condition'] = "undefined-condition"
- iq.send()
-
- if success:
- try:
- self._send_game_list()
- except Exception:
- logging.exception('Failed to send game list after "%s" command', command)
-
- def _send_game_list(self, to=None):
- """Send a massive stanza with the whole game list.
-
- If no target is passed the gamelist is broadcasted to all
- clients.
-
- Arguments:
- to (sleekxmpp.jid.JID): Player to send the game list to.
- If None, the game list will be broadcasted
- """
- games = self.games.get_all_games()
-
- stanza = GameListXmppPlugin()
- for jid in games:
- stanza.add_game(games[jid])
-
- if not to:
- for nick in self.plugin['xep_0045'].getRoster(self.room):
- if nick == self.nick:
- continue
- jid_str = self.plugin['xep_0045'].getJidProperty(self.room, nick, 'jid')
- jid = sleekxmpp.jid.JID(jid_str)
- iq = self.make_iq_result(ito=jid)
- iq.set_payload(stanza)
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send game list to %s", jid)
- else:
- iq = self.make_iq_result(ito=to)
- iq.set_payload(stanza)
- try:
- iq.send(block=False)
- except Exception:
- logging.exception("Failed to send game list to %s", to)
-
-
-def parse_args(args):
- """Parse command line arguments.
-
- Arguments:
- args (dict): Raw command line arguments given to the script
-
- Returns:
- Parsed command line arguments
-
- """
- parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- description="XpartaMuPP - XMPP Multiplayer Game Manager")
-
- log_settings = parser.add_mutually_exclusive_group()
- log_settings.add_argument('-q', '--quiet', help="only log errors", action='store_const',
- dest='log_level', const=logging.ERROR)
- log_settings.add_argument('-d', '--debug', help="log debug messages", action='store_const',
- dest='log_level', const=logging.DEBUG)
- log_settings.add_argument('-v', '--verbose', help="log more informative messages",
- action='store_const', dest='log_level', const=logging.INFO)
- log_settings.set_defaults(log_level=logging.WARNING)
-
- parser.add_argument('-m', '--domain', help="XMPP server to connect to",
- default='lobby.wildfiregames.com')
- parser.add_argument('-l', '--login', help="username for login", default='xpartamupp')
- parser.add_argument('-p', '--password', help="password for login", default='XXXXXX')
- parser.add_argument('-n', '--nickname', help="nickname shown to players", default='WFGBot')
- parser.add_argument('-r', '--room', help="XMPP MUC room to join", default='arena')
- parser.add_argument('-s', '--server', help='address of the ejabberd server',
- action='store', dest='xserver', default=None)
- parser.add_argument('-t', '--disable-tls', help='Pass this argument to connect without TLS encryption',
- action='store_true', dest='xdisabletls', default=False)
-
- return parser.parse_args(args)
-
-
-def main():
- """Entry point a console script."""
- args = parse_args(sys.argv[1:])
-
- logging.basicConfig(level=args.log_level,
- format='%(asctime)s %(levelname)-8s %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S')
-
- xmpp = XpartaMuPP(sleekxmpp.jid.JID('%s@%s/%s' % (args.login, args.domain, 'CC')),
- args.password, args.room + '@conference.' + args.domain, args.nickname)
- xmpp.register_plugin('xep_0030') # Service Discovery
- xmpp.register_plugin('xep_0004') # Data Forms
- xmpp.register_plugin('xep_0045') # Multi-User Chat
- xmpp.register_plugin('xep_0060') # Publish-Subscribe
- xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
-
- if xmpp.connect((args.xserver, 5222) if args.xserver else None, True, not args.xdisabletls):
- xmpp.process()
- else:
- logging.error("Unable to connect")
-
-
-if __name__ == '__main__':
- main()
Index: ps/trunk/source/tools/lobbybots/xpartamupp/stanzas.py
===================================================================
--- ps/trunk/source/tools/lobbybots/xpartamupp/stanzas.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/xpartamupp/stanzas.py (nonexistent)
@@ -1,159 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""0ad-specific XMPP-stanzas."""
-
-from sleekxmpp.xmlstream import ElementBase, ET
-
-
-class BoardListXmppPlugin(ElementBase):
- """Class for custom boardlist and ratinglist stanza extension."""
-
- name = 'query'
- namespace = 'jabber:iq:boardlist'
- interfaces = {'board', 'command'}
- sub_interfaces = interfaces
- plugin_attrib = 'boardlist'
-
- def add_command(self, command):
- """Add a command to the extension.
-
- Arguments:
- command (str): Command to add
- """
- self.xml.append(ET.fromstring('%s' % command))
-
- def add_item(self, name, rating):
- """Add an item to the extension.
-
- Arguments:
- name (str): Name of the player to add
- rating (int): Rating of the player to add
- """
- self.xml.append(ET.Element('board', {'name': name, 'rating': str(rating)}))
-
-
-class GameListXmppPlugin(ElementBase):
- """Class for custom gamelist stanza extension."""
-
- name = 'query'
- namespace = 'jabber:iq:gamelist'
- interfaces = {'game', 'command'}
- sub_interfaces = interfaces
- plugin_attrib = 'gamelist'
-
- def add_game(self, data):
- """Add a game to the extension.
-
- Arguments:
- data (dict): game data to add
- """
- try: del data['ip'] # Don't send the IP address with the gamelist.
- except: pass
-
- self.xml.append(ET.Element('game', data))
-
- def get_game(self):
- """Get game from stanza.
-
- Required to parse incoming stanzas with this extension.
-
- Returns:
- dict with game data
-
- """
- game = self.xml.find('{%s}game' % self.namespace)
- data = {}
-
- if game is not None:
- for key, item in game.items():
- data[key] = item
- return data
-
-
-class GameReportXmppPlugin(ElementBase):
- """Class for custom gamereport stanza extension."""
-
- name = 'report'
- namespace = 'jabber:iq:gamereport'
- plugin_attrib = 'gamereport'
- interfaces = 'game'
- sub_interfaces = interfaces
-
- def add_game(self, game_report):
- """Add a game to the extension.
-
- Arguments:
- game_report (dict): a report about a game
-
- """
- self.xml.append(ET.fromstring(str(game_report)).find('{%s}game' % self.namespace))
-
- def get_game(self):
- """Get game from stanza.
-
- Required to parse incoming stanzas with this extension.
-
- Returns:
- dict with game information
-
- """
- game = self.xml.find('{%s}game' % self.namespace)
- data = {}
-
- if game is not None:
- for key, item in game.items():
- data[key] = item
- return data
-
-
-class ProfileXmppPlugin(ElementBase):
- """Class for custom profile."""
-
- name = 'query'
- namespace = 'jabber:iq:profile'
- interfaces = {'profile', 'command'}
- sub_interfaces = interfaces
- plugin_attrib = 'profile'
-
- def add_command(self, player_nick):
- """Add a command to the extension.
-
- Arguments:
- player_nick (str): the nick of the player the profile is about
-
- """
- self.xml.append(ET.fromstring('%s' % player_nick))
-
- def add_item(self, player, rating, highest_rating=0, # pylint: disable=too-many-arguments
- rank=0, total_games_played=0, wins=0, losses=0):
- """Add an item to the extension.
-
- Arguments:
- player (str): Name of the player
- rating (int): Current rating of the player
- highest_rating (int): Highest rating the player had
- rank (int): Rank of the player
- total_games_played (int): Total number of games the player
- played
- wins (int): Number of won games the player had
- losses (int): Number of lost games the player had
- """
- item_xml = ET.Element('profile', {'player': player, 'rating': str(rating),
- 'highestRating': str(highest_rating), 'rank': str(rank),
- 'totalGamesPlayed': str(total_games_played),
- 'wins': str(wins), 'losses': str(losses)})
- self.xml.append(item_xml)
Index: ps/trunk/source/tools/lobbybots/requirements.txt
===================================================================
--- ps/trunk/source/tools/lobbybots/requirements.txt (revision 27093)
+++ ps/trunk/source/tools/lobbybots/requirements.txt (nonexistent)
@@ -1,3 +0,0 @@
-dnspython
-sleekxmpp
-sqlalchemy
Index: ps/trunk/source/tools/lobbybots/tests/test_echelon.py
===================================================================
--- ps/trunk/source/tools/lobbybots/tests/test_echelon.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/tests/test_echelon.py (nonexistent)
@@ -1,176 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-# pylint: disable=no-self-use
-
-"""Tests for EcheLOn."""
-
-import sys
-
-from argparse import Namespace
-from unittest import TestCase
-from unittest.mock import Mock, call, patch
-
-from parameterized import parameterized
-from sleekxmpp.jid import JID
-from sqlalchemy import create_engine
-
-from xpartamupp.echelon import main, parse_args, Leaderboard
-from xpartamupp.lobby_ranking import Base
-
-
-class TestLeaderboard(TestCase):
- """Test Leaderboard functionality."""
-
- def setUp(self):
- """Set up a leaderboard instance."""
- db_url = 'sqlite://'
- engine = create_engine(db_url)
- Base.metadata.create_all(engine)
- with patch('xpartamupp.echelon.create_engine') as create_engine_mock:
- create_engine_mock.return_value = engine
- self.leaderboard = Leaderboard(db_url)
-
- def test_create_player(self):
- """Test creating a new player."""
- player = self.leaderboard.get_or_create_player(JID('john@localhost'))
- self.assertEqual(player.id, 1)
- self.assertEqual(player.jid, 'john@localhost')
- self.assertEqual(player.rating, -1)
- self.assertEqual(player.highest_rating, None)
- self.assertEqual(player.games, [])
- self.assertEqual(player.games_info, [])
- self.assertEqual(player.games_won, [])
-
- def test_get_profile_no_player(self):
- """Test profile retrieval fro not existing player."""
- profile = self.leaderboard.get_profile(JID('john@localhost'))
- self.assertEqual(profile, dict())
-
- def test_get_profile_player_without_games(self):
- """Test profile retrieval for existing player."""
- self.leaderboard.get_or_create_player(JID('john@localhost'))
- profile = self.leaderboard.get_profile(JID('john@localhost'))
- self.assertDictEqual(profile, {'highestRating': None, 'losses': 0, 'totalGamesPlayed': 0,
- 'wins': 0})
-
-
-class TestReportManager(TestCase):
- """Test ReportManager functionality."""
-
- pass
-
-
-class TestArgumentParsing(TestCase):
- """Test handling of parsing command line parameters."""
-
- @parameterized.expand([
- ([], Namespace(domain='lobby.wildfiregames.com', login='EcheLOn', log_level=30, xserver=None, xdisabletls=False,
- nickname='RatingsBot', password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--debug'],
- Namespace(domain='lobby.wildfiregames.com', login='EcheLOn', log_level=10, xserver=None,xdisabletls=False,
- nickname='RatingsBot', password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--quiet'],
- Namespace(domain='lobby.wildfiregames.com', login='EcheLOn', log_level=40, xserver=None,xdisabletls=False,
- nickname='RatingsBot', password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--verbose'],
- Namespace(domain='lobby.wildfiregames.com', login='EcheLOn', log_level=20, xserver=None, xdisabletls=False,
- nickname='RatingsBot', password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['-m', 'lobby.domain.tld'],
- Namespace(domain='lobby.domain.tld', login='EcheLOn', log_level=30, nickname='RatingsBot', xserver=None, xdisabletls=False,
- password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--domain=lobby.domain.tld'],
- Namespace(domain='lobby.domain.tld', login='EcheLOn', log_level=30, nickname='RatingsBot', xserver=None, xdisabletls=False,
- password='XXXXXX', room='arena',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['-m' 'lobby.domain.tld', '-l', 'bot', '-p', '123456', '-n', 'Bot', '-r', 'arena123',
- '-v'],
- Namespace(domain='lobby.domain.tld', login='bot', log_level=20, nickname='Bot', xserver=None, xdisabletls=False,
- password='123456', room='arena123',
- database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--domain=lobby.domain.tld', '--login=bot', '--password=123456', '--nickname=Bot',
- '--room=arena123', '--database-url=sqlite:////tmp/db.sqlite3', '--verbose'],
- Namespace(domain='lobby.domain.tld', login='bot', log_level=20, nickname='Bot', xserver=None, xdisabletls=False,
- password='123456', room='arena123',
- database_url='sqlite:////tmp/db.sqlite3')),
- ])
- def test_valid(self, cmd_args, expected_args):
- """Test valid parameter combinations."""
- self.assertEqual(parse_args(cmd_args), expected_args)
-
- @parameterized.expand([
- (['-f'],),
- (['--foo'],),
- (['--debug', '--quiet'],),
- (['--quiet', '--verbose'],),
- (['--debug', '--verbose'],),
- (['--debug', '--quiet', '--verbose'],),
- ])
- def test_invalid(self, cmd_args):
- """Test invalid parameter combinations."""
- with self.assertRaises(SystemExit):
- parse_args(cmd_args)
-
-
-class TestMain(TestCase):
- """Test main method."""
-
- def test_success(self):
- """Test successful execution."""
- with patch('xpartamupp.echelon.parse_args') as args_mock, \
- patch('xpartamupp.echelon.Leaderboard') as leaderboard_mock, \
- patch('xpartamupp.echelon.EcheLOn') as xmpp_mock:
- args_mock.return_value = Mock(log_level=30, login='EcheLOn',
- domain='lobby.wildfiregames.com', password='XXXXXX',
- room='arena', nickname='RatingsBot',
- database_url='sqlite:///lobby_rankings.sqlite3',
- xserver=None, xdisabletls=False)
- main()
- args_mock.assert_called_once_with(sys.argv[1:])
- leaderboard_mock.assert_called_once_with('sqlite:///lobby_rankings.sqlite3')
- xmpp_mock().register_plugin.assert_has_calls([call('xep_0004'), call('xep_0030'),
- call('xep_0045'), call('xep_0060'),
- call('xep_0199', {'keepalive': True})],
- any_order=True)
- xmpp_mock().connect.assert_called_once_with(None, True, True)
- xmpp_mock().process.assert_called_once_with()
-
- def test_failing_connect(self):
- """Test failing connect to XMPP server."""
- with patch('xpartamupp.echelon.parse_args') as args_mock, \
- patch('xpartamupp.echelon.Leaderboard') as leaderboard_mock, \
- patch('xpartamupp.echelon.EcheLOn') as xmpp_mock:
- args_mock.return_value = Mock(log_level=30, login='EcheLOn',
- domain='lobby.wildfiregames.com', password='XXXXXX',
- room='arena', nickname='RatingsBot',
- database_url='sqlite:///lobby_rankings.sqlite3',
- xserver=None, xdisabletls=False)
-
- xmpp_mock().connect.return_value = False
- main()
- args_mock.assert_called_once_with(sys.argv[1:])
- leaderboard_mock.assert_called_once_with('sqlite:///lobby_rankings.sqlite3')
- xmpp_mock().register_plugin.assert_has_calls([call('xep_0004'), call('xep_0030'),
- call('xep_0045'), call('xep_0060'),
- call('xep_0199', {'keepalive': True})],
- any_order=True)
- xmpp_mock().connect.assert_called_once_with(None, True, True)
- xmpp_mock().process.assert_not_called()
Index: ps/trunk/source/tools/lobbybots/tests/test_elo.py
===================================================================
--- ps/trunk/source/tools/lobbybots/tests/test_elo.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/tests/test_elo.py (nonexistent)
@@ -1,150 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""Tests for the ELO-implementation."""
-
-from unittest import TestCase
-
-from hypothesis import assume, example, given
-from hypothesis import strategies as st
-from parameterized import parameterized
-
-from xpartamupp.elo import (get_rating_adjustment, ANTI_INFLATION, ELO_K_FACTOR_CONSTANT_RATING,
- ELO_SURE_WIN_DIFFERENCE, VOLATILITY_CONSTANT)
-
-
-class TestELO(TestCase):
- """Test behavior of ELO calculation."""
-
- @parameterized.expand([
- ([1000, 1000, 0, 0, 1], 82),
- ([1000, 1000, 0, 0, -1], -83),
- ([1000, 1000, 0, 0, 0], 0),
- ([1200, 1200, 0, 0, 1], 78),
- ([1200, 1200, 0, 0, -1], -78),
- ([1200, 1200, 0, 0, 0], 0),
- ([1200, 1200, 1, 0, 1], 65),
- ([1200, 1200, 1, 0, 0], 0),
- ([1200, 1200, 1, 0, -1], -65),
- ([1200, 1200, 100, 0, 1], 16),
- ([1200, 1200, 100, 0, 0], 0),
- ([1200, 1200, 100, 0, -1], -16),
- ([1200, 1200, 1000, 0, 1], 16),
- ([1200, 1200, 1000, 0, 0], 0),
- ([1200, 1200, 1000, 0, -1], -16),
- ([1200, 1200, 0, 1, 1], 78),
- ([1200, 1200, 0, 1, 0], 0),
- ([1200, 1200, 0, 1, -1], -78),
- ([1200, 1200, 0, 100, 1], 78),
- ([1200, 1200, 0, 100, 0], 0),
- ([1200, 1200, 0, 100, -1], -78),
- ([1200, 1200, 0, 1000, 1], 78),
- ([1200, 1200, 0, 1000, 0], 0),
- ([1200, 1200, 0, 1000, -1], -78),
- ([1400, 1000, 0, 0, 1], 24),
- ([1400, 1000, 0, 0, 0], -49),
- ([1400, 1000, 0, 0, -1], -122),
- ([1000, 1400, 0, 0, 1], 137),
- ([1000, 1400, 0, 0, 0], 55),
- ([1000, 1400, 0, 0, -1], -28),
- ([2200, 2300, 0, 0, 1], 70),
- ([2200, 2300, 0, 0, 0], 10),
- ([2200, 2300, 0, 0, -1], -50),
- ])
- def test_valid_adjustments(self, args, expected_adjustment):
- """Test correctness of valid rating adjustments."""
- self.assertEqual(get_rating_adjustment(*args), expected_adjustment)
-
- @given(st.integers(min_value=ELO_K_FACTOR_CONSTANT_RATING),
- st.integers(min_value=-2099, max_value=ELO_SURE_WIN_DIFFERENCE - 1), st.integers(),
- st.integers(),
- st.integers(min_value=-1, max_value=1))
- @example(ELO_K_FACTOR_CONSTANT_RATING + 300, 0, 0, 0, 1)
- def test_constant_rating(self, rating_player1, difference_player2, played_games_player1,
- played_games_player2, result):
- """Test that points gained are constant above a threshold."""
- volatility = 50.0 * (min(max(0, played_games_player1), VOLATILITY_CONSTANT) /
- VOLATILITY_CONSTANT + 0.25) / 1.25
- rating_adjustment = (difference_player2 + result * ELO_SURE_WIN_DIFFERENCE) / volatility \
- - ANTI_INFLATION
- if result == 1:
- expected_adjustment = max(0.0, rating_adjustment)
- elif result == -1:
- expected_adjustment = min(0.0, rating_adjustment)
- else:
- expected_adjustment = rating_adjustment
-
- self.assertEqual(get_rating_adjustment(rating_player1, rating_player1 + difference_player2,
- played_games_player1, played_games_player2, result),
- round(expected_adjustment))
-
- @given(st.data())
- def test_sure_win(self, data):
- """Test behavior if winning player 1 has >600 points more.
-
- In this case the winning player shouldn't gain points, as it
- was a "sure win" and the loosing player shouldn't loose
- points.
- """
- rating_player1 = data.draw(st.integers(min_value=-1599))
- difference_player2 = data.draw(st.integers(min_value=ELO_SURE_WIN_DIFFERENCE))
- assume(rating_player1 - difference_player2 > -2200)
- played_games_player1 = data.draw(st.integers())
- played_games_player2 = data.draw(st.integers())
-
- self.assertEqual(get_rating_adjustment(rating_player1,
- rating_player1 - difference_player2,
- played_games_player1, played_games_player2, 1),
- 0)
- self.assertEqual(get_rating_adjustment(rating_player1 - difference_player2,
- rating_player1, played_games_player2,
- played_games_player1, -1), 0)
-
- @given(st.integers(min_value=-2199), st.integers(min_value=ELO_SURE_WIN_DIFFERENCE),
- st.integers(),
- st.integers())
- @example(1000, ELO_SURE_WIN_DIFFERENCE, 0, 0)
- def test_sure_loss(self, rating_player1, difference_player2, played_games_player1,
- played_games_player2):
- """Test behavior if winning player 2 has >600 points more.
-
- In this case the winning player shouldn't gain points, as it
- was a "sure win" and the loosing player shouldn't loose
- points.
- """
- self.assertEqual(get_rating_adjustment(rating_player1,
- rating_player1 - difference_player2 * -1,
- played_games_player1, played_games_player2, -1),
- 0)
- self.assertEqual(get_rating_adjustment(rating_player1 - difference_player2 * -1,
- rating_player1, played_games_player2,
- played_games_player1, 1), 0)
-
- @given(st.integers(max_value=-2200), st.integers(),
- st.integers(),
- st.integers(),
- st.one_of(st.just(1), st.just(-1)))
- @example(-2200, 2000, 0, 0, 1)
- @example(2000, -2200, 0, 0, 1)
- def test_minus_2200_bug_workaround(self, rating_player1, rating_player2,
- played_games_player1, played_games_player2, result):
- """Test workaround for -2200 bug."""
- with self.assertRaises(ValueError):
- get_rating_adjustment(rating_player1, rating_player2, played_games_player1,
- played_games_player2, result)
- with self.assertRaises(ValueError):
- get_rating_adjustment(rating_player2, rating_player1, played_games_player1,
- played_games_player2, result)
Index: ps/trunk/source/tools/lobbybots/tests/__init__.py
===================================================================
Index: ps/trunk/source/tools/lobbybots/tests/test_utils.py
===================================================================
--- ps/trunk/source/tools/lobbybots/tests/test_utils.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/tests/test_utils.py (nonexistent)
@@ -1,45 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-"""Tests for utility functions."""
-
-from unittest import TestCase
-
-from hypothesis import given
-from hypothesis import strategies as st
-
-from xpartamupp.utils import LimitedSizeDict
-
-
-class TestLimitedSizeDict(TestCase):
- """Test limited size dict."""
-
- @given(st.integers(min_value=2, max_value=2**10))
- def test_max_items(self, size_limit):
- """Test max items of dicts.
-
- Test that the dict doesn't grow indefinitely and that the
- oldest entries are removed first.
- """
- test_dict = LimitedSizeDict(size_limit=size_limit)
- for i in range(size_limit):
- test_dict[i] = i
- self.assertEqual(size_limit, len(test_dict))
- test_dict[size_limit + 1] = size_limit + 1
- self.assertEqual(size_limit, len(test_dict))
- self.assertFalse(0 in test_dict.values())
- self.assertTrue(1 in test_dict.values())
- self.assertTrue(size_limit + 1 in test_dict.values())
Index: ps/trunk/source/tools/lobbybots/tests/test_lobby_ranking.py
===================================================================
--- ps/trunk/source/tools/lobbybots/tests/test_lobby_ranking.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/tests/test_lobby_ranking.py (nonexistent)
@@ -1,70 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-# pylint: disable=no-self-use
-
-"""Tests for the database schema."""
-
-import sys
-
-from argparse import Namespace
-from unittest import TestCase
-from unittest.mock import Mock, patch
-
-from parameterized import parameterized
-
-from xpartamupp.lobby_ranking import main, parse_args
-
-
-class TestArgumentParsing(TestCase):
- """Test handling of parsing command line parameters."""
-
- @parameterized.expand([
- (['create'], Namespace(action='create', database_url='sqlite:///lobby_rankings.sqlite3')),
- (['--database-url', 'sqlite:////tmp/db.sqlite3', 'create'],
- Namespace(action='create', database_url='sqlite:////tmp/db.sqlite3')),
- ])
- def test_valid(self, cmd_args, expected_args):
- """Test valid parameter combinations."""
- self.assertEqual(parse_args(cmd_args), expected_args)
-
- @parameterized.expand([
- ([],),
- (['--database-url=sqlite:////tmp/db.sqlite3'],),
- ])
- def test_missing_action(self, cmd_args):
- """Test invalid parameter combinations."""
- with self.assertRaises(SystemExit):
- parse_args(cmd_args)
-
-
-class TestMain(TestCase):
- """Test main method."""
-
- def test_success(self):
- """Test successful execution."""
- with patch('xpartamupp.lobby_ranking.parse_args') as args_mock, \
- patch('xpartamupp.lobby_ranking.create_engine') as create_engine_mock, \
- patch('xpartamupp.lobby_ranking.Base') as declarative_base_mock:
- args_mock.return_value = Mock(action='create',
- database_url='sqlite:///lobby_rankings.sqlite3')
- engine_mock = Mock()
- create_engine_mock.return_value = engine_mock
- main()
- args_mock.assert_called_once_with(sys.argv[1:])
- create_engine_mock.assert_called_once_with(
- 'sqlite:///lobby_rankings.sqlite3')
- declarative_base_mock.metadata.create_all.assert_any_call(engine_mock)
Index: ps/trunk/source/tools/lobbybots/tests/test_xpartamupp.py
===================================================================
--- ps/trunk/source/tools/lobbybots/tests/test_xpartamupp.py (revision 27093)
+++ ps/trunk/source/tools/lobbybots/tests/test_xpartamupp.py (nonexistent)
@@ -1,179 +0,0 @@
-# Copyright (C) 2021 Wildfire Games.
-# This file is part of 0 A.D.
-#
-# 0 A.D. is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# 0 A.D. is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with 0 A.D. If not, see .
-
-# pylint: disable=no-self-use
-
-"""Tests for XPartaMuPP."""
-
-import sys
-
-from argparse import Namespace
-from unittest import TestCase
-from unittest.mock import Mock, call, patch
-
-from parameterized import parameterized
-from sleekxmpp.jid import JID
-
-from xpartamupp.xpartamupp import Games, main, parse_args
-
-
-class TestGames(TestCase):
- """Test Games class responsible for holding active games."""
-
- def test_add(self):
- """Test successfully adding a game."""
- games = Games()
- jid = JID(jid='player1@domain.tld')
- # TODO: Check how the real format of data looks like
- game_data = {'players': ['player1', 'player2'], 'nbp': 'foo', 'state': 'init'}
- self.assertTrue(games.add_game(jid, game_data))
- all_games = games.get_all_games()
- game_data.update({'players-init': game_data['players'], 'nbp-init': game_data['nbp'],
- 'state': game_data['state']})
- self.assertDictEqual(all_games, {jid: game_data})
-
- @parameterized.expand([
- ('', {}),
- ('player1@domain.tld', {}),
- ('player1@domain.tld', None),
- ('player1@domain.tld', ''),
- ])
- def test_add_invalid(self, jid, game_data):
- """Test trying to add games with invalid data."""
- games = Games()
- self.assertFalse(games.add_game(jid, game_data))
-
- def test_remove(self):
- """Test removal of games."""
- games = Games()
- jid1 = JID(jid='player1@domain.tld')
- jid2 = JID(jid='player3@domain.tld')
- # TODO: Check how the real format of data looks like
- game_data1 = {'players': ['player1', 'player2'], 'nbp': 'foo', 'state': 'init'}
- games.add_game(jid1, game_data1)
- game_data2 = {'players': ['player3', 'player4'], 'nbp': 'bar', 'state': 'init'}
- games.add_game(jid2, game_data2)
- game_data1.update({'players-init': game_data1['players'], 'nbp-init': game_data1['nbp'],
- 'state': game_data1['state']})
- game_data2.update({'players-init': game_data2['players'], 'nbp-init': game_data2['nbp'],
- 'state': game_data2['state']})
- self.assertDictEqual(games.get_all_games(), {jid1: game_data1, jid2: game_data2})
- games.remove_game(jid1)
- self.assertDictEqual(games.get_all_games(), {jid2: game_data2})
- games.remove_game(jid2)
- self.assertDictEqual(games.get_all_games(), dict())
-
- def test_remove_unknown(self):
- """Test removal of a game, which doesn't exist."""
- games = Games()
- jid = JID(jid='player1@domain.tld')
- # TODO: Check how the real format of data looks like
- game_data = {'players': ['player1', 'player2'], 'nbp': 'foo', 'state': 'init'}
- games.add_game(jid, game_data)
- self.assertFalse(games.remove_game(JID('foo@bar.tld')))
-
- def test_change_state(self):
- """Test state changes of a games."""
- pass
- # slightly unknown how to do that properly, as some data structures aren't known
-
-
-class TestArgumentParsing(TestCase):
- """Test handling of parsing command line parameters."""
-
- @parameterized.expand([
- ([], Namespace(domain='lobby.wildfiregames.com', login='xpartamupp', log_level=30, xserver=None, xdisabletls=False,
- nickname='WFGBot', password='XXXXXX', room='arena')),
- (['--debug'],
- Namespace(domain='lobby.wildfiregames.com', login='xpartamupp', log_level=10, xserver=None, xdisabletls=False,
- nickname='WFGBot', password='XXXXXX', room='arena')),
- (['--quiet'],
- Namespace(domain='lobby.wildfiregames.com', login='xpartamupp', log_level=40, xserver=None, xdisabletls=False,
- nickname='WFGBot', password='XXXXXX', room='arena')),
- (['--verbose'],
- Namespace(domain='lobby.wildfiregames.com', login='xpartamupp', log_level=20, xserver=None, xdisabletls=False,
- nickname='WFGBot', password='XXXXXX', room='arena')),
- (['-m', 'lobby.domain.tld'],
- Namespace(domain='lobby.domain.tld', login='xpartamupp', log_level=30, nickname='WFGBot', xserver=None, xdisabletls=False,
- password='XXXXXX', room='arena')),
- (['--domain=lobby.domain.tld'],
- Namespace(domain='lobby.domain.tld', login='xpartamupp', log_level=30, nickname='WFGBot', xserver=None, xdisabletls=False,
- password='XXXXXX', room='arena')),
- (['-m' 'lobby.domain.tld', '-l', 'bot', '-p', '123456', '-n', 'Bot', '-r', 'arena123',
- '-v'],
- Namespace(domain='lobby.domain.tld', login='bot', log_level=20, xserver=None, xdisabletls=False,
- nickname='Bot', password='123456', room='arena123')),
- (['--domain=lobby.domain.tld', '--login=bot', '--password=123456', '--nickname=Bot',
- '--room=arena123', '--verbose'],
- Namespace(domain='lobby.domain.tld', login='bot', log_level=20, xserver=None, xdisabletls=False,
- nickname='Bot', password='123456', room='arena123')),
- ])
- def test_valid(self, cmd_args, expected_args):
- """Test valid parameter combinations."""
- self.assertEqual(parse_args(cmd_args), expected_args)
-
- @parameterized.expand([
- (['-f'],),
- (['--foo'],),
- (['--debug', '--quiet'],),
- (['--quiet', '--verbose'],),
- (['--debug', '--verbose'],),
- (['--debug', '--quiet', '--verbose'],),
- ])
- def test_invalid(self, cmd_args):
- """Test invalid parameter combinations."""
- with self.assertRaises(SystemExit):
- parse_args(cmd_args)
-
-
-class TestMain(TestCase):
- """Test main method."""
-
- def test_success(self):
- """Test successful execution."""
- with patch('xpartamupp.xpartamupp.parse_args') as args_mock, \
- patch('xpartamupp.xpartamupp.XpartaMuPP') as xmpp_mock:
- args_mock.return_value = Mock(log_level=30, login='xpartamupp',
- domain='lobby.wildfiregames.com', password='XXXXXX',
- room='arena', nickname='WFGBot',
- xserver=None, xdisabletls=False)
- main()
- args_mock.assert_called_once_with(sys.argv[1:])
- xmpp_mock().register_plugin.assert_has_calls([call('xep_0004'), call('xep_0030'),
- call('xep_0045'), call('xep_0060'),
- call('xep_0199', {'keepalive': True})],
- any_order=True)
- xmpp_mock().connect.assert_called_once_with(None, True, True)
- xmpp_mock().process.assert_called_once_with()
-
- def test_failing_connect(self):
- """Test failing connect to XMPP server."""
- with patch('xpartamupp.xpartamupp.parse_args') as args_mock, \
- patch('xpartamupp.xpartamupp.XpartaMuPP') as xmpp_mock:
- args_mock.return_value = Mock(log_level=30, login='xpartamupp',
- domain='lobby.wildfiregames.com', password='XXXXXX',
- room='arena', nickname='WFGBot',
- xserver=None, xdisabletls=False)
-
- xmpp_mock().connect.return_value = False
- main()
- args_mock.assert_called_once_with(sys.argv[1:])
- xmpp_mock().register_plugin.assert_has_calls([call('xep_0004'), call('xep_0030'),
- call('xep_0045'), call('xep_0060'),
- call('xep_0199', {'keepalive': True})],
- any_order=True)
- xmpp_mock().connect.assert_called_once_with(None, True, True)
- xmpp_mock().process.assert_not_called()