Welcome to piqueserver’s documentation!

Piqueserver is an Ace of Spades 0.75 Server based on Pysnip.

Installation

Note

piqueserver only supports python 3.7 and above

All platforms

Installing with pip

pip3 install piqueserver # vanilla install
pip3 install piqueserver[ssh] # if you also want ssh server

Unix-like

Installing from source

# required for https, pillow, map compression etc.
# these are for ubuntu 16.04, find similar packages for your own distro/OSs
sudo apt-get install python3-dev libssl-dev libffi-dev libjpeg-dev zlib1g-dev
# get the source
git clone https://github.com/piqueserver/piqueserver
cd piqueserver
# we make git tags for every version so you can checkout out to specific version if you want
# git checkout v0.1.3
# create a new python3 venv
virtualenv -p python3 venv
source venv/bin/activate
# install deps.
pip install -r requirements.txt
# install piqueserver
python setup.py install

# don't forget to deactivate the venv when finished!
deactivate

Windows

Installation from source

Note

Most of the piqueserver team uses Linux and we aren’t experienced with Cython on windows. If you can help us improve windows support we’d greatly appreciate it.

Tricky bit for windows is to get Cython working.

  • Install Visual C++ compiler please follow this guide.
  • Don’t forget to upgrade setuptools
  • Install git or download sources from github and unzip
  • If you decided to use git: git clone https://github.com/piqueserver/piqueserver

Tip

If you see errors like “unable to find vcvarsall.bat” refer to this article.

cd piqueserver
pip3 install -r requirements.txt
python setup.py install

Configuring Piqueserver

Piqueserver is configured through a config file, usually named config.toml. Piqueserver uses the configuration language TOML (Tom’s Obvious Markup Language). A detailed description of TOML can be found in the TOML readme, but a short example is provided below.

Note

PySpades, PySnip and versions of piqueserver before 1.0.0 used a JSON file for settings. These files will still be read, if no config.toml is available. However, the structure piqueserver expects might differ.

TOML Syntax

TOML files are plaintext files. They can be edited with any plaintext editor of your choice, such as Notepad++ on windows, editor on mac and, nano or gedit on Linux.

Configuration is case-sensitive. This means that your settings will not apply correctly if you use the incorrect case.

The section header is followed by key-value pairs:

name = "best server ever"

If a # is encountered, the rest of the line will be ignored.

There are a number of types of values:

text = "text, surrounded by quotes"

motd = """
a longer text
that can stretch
over more lines
(useful for the MOTD!)"""

list = ["a", "list",
        "of", "values"]

a_number = 123.5
enable_thing = true  # or false

TOML supports sections similar to the Microsoft .ini file format. The beginning of a section is marked by the name of the section surrounded by square brackets:

[general]

Any key-value pairs that follow this are part of that section, until a new section is started.

Substitution

Some config options allow data about the server to be substituted. These options are enclosed in curly braces. This example uses all substitutions that are available when substitution is available for a config option:

motd = [
    "Welcome to {server_name}! See /help for commands.",
    "you are playing {game_mode}",
    "Map is {map_name} by {map_author}.",
    "Map description: {map_description}.",
    "(server powered by piqueserver)",
]

Individual config options might have additional substitutions available. This is noted in the documentation for the relevant option.

New in version 1.0.0: The {name} format was added. The older, %(name)s format is deprecated.

Config directory structure

The configuration directory follows a strict structure.

By default the configuration directory lives at $XDG_CONFIG_HOME/piqueserver/ with a fallback to $HOME/.config/piqueserver/, as specified by the XDG Base Directory Specification.

This can be overridden by the -d command line option.

.
├── bans.txt               # Player bans get stored in this file; it is created automatically.
├── config.toml            # The main configuration file. By default piqueserver will look for config.toml in the config directory.
│                          # This can be overridden by the -c command line option.
├── game_modes             # Scripts that are specifically game modes will be stored here.
│   └── buildandsplat.py   # Your custom game mode script
├── logs                   # This directory contains logs output by the server.
│   └── log.txt            # The main server logfile; If you disable daily rotation of log files all logs go here.
├── maps                   # Any map files should be stored in here. Where a map is specified by name in "rotation" config option
│   ├── classicgen.txt     # the server will look for  a .txt or .txt/.vxl file with that name.
│   └── random.txt
├── scripts                # You can add other scripts not included with piqueserver here.
│   │                      # The server will load any scripts listed in "scripts" config option by name from here.
│   └── showhp.py          # For example, if "showhp" is listed in the "scripts" config option, then scripts/showhp.py will be loaded.
│
└── data                   # Various other data files are stored here.
    └── GeoLiteCity.dat    # Currently it's only used for the geoip data file required by the /from command in-game.

Options

Base Configuration Options

These are the base configuration options available in piqueserver.

General

This section contains general information and configuration for the server

name

The Name of the server, as displayed on the master server list:

name = "piqueserver instance"

This config option supports Substitution.

motd

A list of lines which should be sent when the player joins:

motd = [
   "Welcome to the server",
   "Have fun! <3",
]

This config option supports Substitution.

help

This is the text shown by the /help command. If not defined, /help will just display a list of the available commands.

This config option supports Substitution.

rules

This is the text shown by the /rules command.

This config option supports Substitution.

tips

A line picked at random from this text will be shown every tip_frequency minutes.

This config option supports Substitution.

tip_frequency

How often the tip will be shown, in minutes. 0 means no tips. Default 5.

max_players

The maximum number of players on the server. Going over 32 players will break the vanilla AoS client, as is not designed to support more. Default 32.

game_mode

The ctf for “Capture the Flag”, tc for “Territory Control”. or a script name for a custom game mode.

cap_limit

The number of captures that are required to end the game.

rotation

A list of maps to use

default_time_limit

The default time limit to set per map. When the time limit runs out, the map rotation is advanced.

advance_on_win

If true, advance the map rotation when the game ends.

random_rotation

false if the order should be as in rotation, true if the order should be shuffled. Default false.

max_connections_per_ip

Limits how many players can connect from the same IP address. 0 disables this limit. Default 0.

team1/team2

This section configures the teams:

[team1]
# name of the team to be displayed in-game
name = "Blue"
# color of the players in RGB, 0-255
color = [ 0, 0, 255]

[team2]
name = "Green"
color = [ 0, 255, 0]
bans

This section defines the behaviour when admins ban players:

[bans]
# default duration a banned player will be banned for
default_duration = "1day"

# location the bans are saved and loaded from
file = "bans.txt"

# Ban publish allows you to synchronize bans between servers. When enabled,
# the server listens on the given port and responds to any requests with a list
# of bans
publish = false
publish_port = 32885

# Bansubscribe allows you to inherit bans from another server with banpublish enabled.
# `url` is the URL returning the json list, `whitelist` is a list of names which should
# be exempt from the filter
bansubscribe = [
    { url = "http://www.blacklist.spadille.net/subscribe.json", whitelist = []},
]

# how often the subscribed servers are frequented to update bans
bansubscribe_interval = "5min"
respawn_waves

When true, respawn all dead players every respawn_time seconds. When false, respawn a player respawn_time seconds after their death. Spawning in groups decreases the effectiveness of spawnkilling. Default true.

respawn_time

see respawn_waves

cap_limit

The number of intel captures before the game is won. Default 10.

respawn_time

The number of seconds before a player respawns. Default 5.

master

‘true’ shows server on the master serverlist. ‘false’ disables this, for private games want a private game. Default false.

friendly_fire

true: enables friendly fire. false: disables friendly fire.

friendly_fire_on_grief

if true, friendly fire is enabled temporarily if a player destroys a block.

spade_teamkills_on_grief

If friendly fire should be enabled for the spade too. This is disabled by default, because it often causes accidental teamkills in tunnels.

grief_friendly_fire_time

The number of seconds a player is vulnerable to friendly fire after destroying a block when friendly_fire_on_grief is enabled.

teamswitch_interval

Forces players to wait a set duration before being able to switch back again after they switched teams.

“0sec” disables the cooldown.

teamswitch_allowed

If true you only get to pick a team when you join.

detect_speedhack

If true, attempt to detect if users are speedhacking. This is not 100% accurate, so it might be a good idea to disable it for servers where the users are trusted.

rubberband_distance

Distance the server tolerates between the place it thinks the client is to where the client actually is. Default 10.

melee_damage

The amount of damage dealt by the spade. Default 80.

fall_damage

controls whether players receive damage by falling from height

passwords

This section contains roles and their associated passwords. When playing, a player may gain that role by typing:

/login <password>

Any name may be used for a role, but there are two special roles: admin and trusted. Users with the admin role have the maximum rights available, while trusted users are not affected by votekicks and similar.

Each account can have a list of passwords. It is usually a good idea to give out one password per person:

[passwords]
admin = ["password","sesame","watermelon"],
trusted = ["semi","coolness"]
custom = ["mypass"]
rights

This section contains the commands each role can execute.

scripts

Piqueserver ships with a set of scripts you can use to customize the features of your server. They are loaded in order, “on top of” each other.

Scripts can either be absolute python import paths (piqueserver.scripts.aimbot2) or the name of scripts in the scripts folder, excluding the file extension (mycustomscript for a script at scripts/mycustomscript.py):

scripts = [
    "piqueserver.scripts.rollback",
    "piqueserver.scripts.protect",
    "myscript",
]
Logging

Piqueserver can log events that happen to a text file:

[logging]
# set log level
# log levels in decending order: debug, info, warn, error, critical
loglevel = "info"
# the logfile to log to
# relative paths are resolved relative to the config directory; parent
# directories are created as necessary
# empty string disables logging
logfile = "./logs/log.txt"

# Write a new log file each day
rotate_daily = true

# enable profiling
profile = false
ssh

This section controls SSH “manhole” access to the server. This allows you access to a python shell. This is mainly useful for debugging. If you don’t know what you are doing, you should leave it disabled:

[ssh]
enabled = false
port = 32887

[ssh.users]
# user = password
# pairs for credentials allowed to login to the ssh server
# WARNING: keep these credentials secure since this gives console access to the server
# on which piqueserver is running!
user1 = "ssh_pass_change_this"
status_server

The status server is a built-in web server that displays information about the server and connected players:

[status_server]
enabled = true
# the port to listen on
port = 32886
# write an access log
logging = false
server_prefix

When the server sends messages to users, the message is prefixed with the characters in server_prefix.

user_blocks_only

Controls whether users can affect the map’s initial blocks.

logfile

The file where the server log is recorded. Relative paths are resolved relative to the configuration directory. For example if the logfile is /logs/log.txt, this will be in the logs subdirectory of the config directory.

balanced_teams

If 0, any permutation of teams is allowed. If 1 or greater, players are not allowed to join a team if that would mean the difference in players count between the two teams is more than the balanced_teams value. Default 2.

login_retries

The number of /login attempts allowed before users are auto-kicked. Default 3.

irc

This section configures an IRC chatbot that reports server events in the given channel:

[irc]
enabled = false
# IRC login details
nickname = "piqueserver"
username = "piqueserver"
realname = "piqueserver"
server = "irc.quakenet.org"
port = 6667

# channel to join into
channel = "#piquserver-bots"
# password for the channel
password = ""

# prefixes irc users must use for bot to process as command or to send to game chat
commandprefix = "!"
chatprefix = "."
set_god_build

Put the player into god build mode automatically when entering god mode

time_announcements

Configure the times that announcements about the remaining time should be made. This value is a list of times remaining in seconds.

ip_getter

Optionally override the service used to fetch the server’s public ip address. Eg. "https://api.ipify.org". If this is set to an empty string, IP getting is disabled.

Note: this url must return solely the ip address in the response body.

release_notifications

Check github for new releases and notify admins if new releases are found. Default True.

everyone_is_admin

Set everyone_is_admin to true to automatically log all players in as admin on join. Possibly useful for testing purposes.

Supported Python environments

Piqueserver supports the following Versions of CPython: 3.7, 3.8, 3.9, 3.10 and 3.11

Migration from Python 2 to Python 3

To Migrate an existing installations over, follow the following steps:

  1. Make sure Python3.7+ is installed on your system: python3 --version
  2. Make sure the Python3 version of pip is installed on your system: pip3 --version
  3. pip uninstall piqueserver
  4. pip3 install --upgrade piqueserver
  5. Start up your server again and verify no errors are shown in the console

Possible issues with the migration

Python3 has changed many internals of the python language. Because of that, some subtle bugs might occur.

Old pyspades scripts will likely be broken by this change too. If this happens, just open an issue or chat with us and we will work with you to get it ported to the new version!

See also

Porting scripts

Included Piqueserver Scripts

This is an Overview over the scripts that currently ship with piqueserver

Scripts

piqueserver.scripts.afk script

Kicks a player if inactive for too long.

Options
[afk]
time_limit = "15min"

Code author: hompy

piqueserver.scripts.aimbot2 script

Detects and reacts to possible aimbot users.

Commands
  • /accuracy <player> shows player’s accuracy per weapon
  • /hackinfo <player> shows player’s accuracy, K/D ratio and how often their cross-hair snaps onto another players head admin only
Options
[aimbot]
collect_data = true # saves hits and shots of each weapon to a csv file

Code author: ?

piqueserver.scripts.airstrike2 script

Airstrikes. Boom!

Commands
  • /airstrike or /a allows a player to call an airstike
  • /givestrike <player> gives a player the ability to call an airstike immediately admin only

Code author: hompy

piqueserver.scripts.antijerk script

Kicks jerks for ‘PRESS ALT-F4 FOR AIRSTRIKES’ and so on.

Options
[antijerk]
ban_duration = "15min"

Code author: ?

piqueserver.scripts.autohelp script

Helps Deuces automagically when they ask in the chat for help.

Code author: ?

piqueserver.game_modes.babel_script module

Original script by Yourself Anti grief by izzy Return intel dropped from platform bug fix by a_girl

Release thread: http://www.buildandshoot.com/viewtopic.php?t=2586

How to install and configure:

  1. Set game_mode to “piqueserver.game_mode.babel” in config.txt
  2. Add “piqueserver.scripts.babel_script” to scripts list in config.txt
  3. Set cap_limit to “10” in config

piqueserver.scripts.badmin script

Badmin is an bot admin.

It automates common admin tasks such as:

  • Banning griefers
  • Banning aimbotters
  • Banning racists

Since it is automatic it won’t always get it right.

Commands
  • /badmin shows which badmin options are enabled/disabled admin only
  • /investigate <player> shows a player’s grief score, K/D ratio and hit accuracy admin only

Note

It depends on the blockinfo.py (for grief detection), ratio.py (for k/d ratio), aimbot2.py (hit accuracy) scripts.

Code author: ?

piqueserver.scripts.blockinfo script

A tool for identifying griefers.

Note

“blockinfo” must be AFTER “votekick” in the config script list

Commands
  • /griefcheck or /gc <player> <minutes> gives you when, how many and whos blocks a player destroyed admin only
Options
[blockinfo]
griefcheck_on_votekick = true
irc_only = false

Code author: hompy

piqueserver.scripts.daycycle script

Gives Ace of Spades a daycycle (using the fog).

Commands
  • /dayspeed <speed> sets day speed admin only
  • /dayspeed gets day speed admin only
  • /daytime <time> sets day time admin only
  • /daytime gets day time

Code author: hompy

piqueserver.scripts.demolitionman script

Restocks the user when reloading / throwing a nade.

Commands
  • /toggledemo toggles demolition

Code author: learn_more (MIT LICENSE)

piqueserver.scripts.dirtnade script

Makes grenades create blocks.

Code author: hompy

piqueserver.scripts.disco script

Ever wanted a disco in Ace of Spades?

Commands
  • /disco toggles disco

Code author: mat^2

piqueserver.scripts.flagreturn script

Makes the flag returnable in Quake-like fashion.

Code author: mat^2 & learn_more

piqueserver.scripts.geoip script

Gets a player’s location info using a geoip database. Location is formatted as a list of regions ordered by hierarchy (i.e. City, Region, Country)

Note

This script depends on geoip2 package and piqueserver --update-geoip needs to be executed after installing the package.

Commands
  • /from get active player’s location
  • /from <player> get player’s location info

Code author: ?

piqueserver.scripts.grownade script

Turns your grenades into magical kv6-growing seeds. Or beans!

Use /model <filename> to load model files. These must be placed inside a folder named kv6/ in the config directory. A full path would look like this: ~/config/kv6/filename.kv6. Then it can be loaded with /model some_model

Wildcards and subfolders are allowed. Some examples:

/model building*

Loads all models that start with “building”, like “building1” and “buildingred”:

/model my_trees/*

Loads all models inside a folder named “my_trees”

When you have many models loaded, each grenade will pick a random one to grow. To stop littering the map with random objects just type /model.

Pivot

THE PIVOT POINT in kv6 files determines the first block to be placed. The rest of the model will then follow, growing around it.

The pivot point MUST be sitting on a block, or the model won’t load. Checking ‘Adjust pivots’ in the Tools menu in SLAB6 will show handy coordinates. To be sure, you can move the pivot to half of a block, e.g.: (4.50, 4.50, 8.50)

In a tree kv6, for example, the pivot point would lie on the lowest block of the tree trunk, so that it grows up and not into the ground.

Works best with 0.75 kv6 models.

You can adjust FLYING_MODELS and GROW_ON_WATER to allow growing in the air and on water, respectively. These are disabled by default so you can fly high and sprinkle tree-growing grenades without worrying about unseemly oddities.

Options
[grownade]

flying_models = False # if False grenades exploding in midair will be ignored
grow_on_water = False # if False grenades exploding in water will do nothing

Code author: hompy

piqueserver.scripts.map_extensions script

Provides extensions to the map metadata (e.g. water damage).

Code author: ?

piqueserver.scripts.mapmakingtools script

piqueserver.scripts.markers script

Pinpoint players and bookmark tunnels without making the enemy aware. Markers attempt to help teams communicate and so, cooperate.

Markers are just symbols built with blocks at the very top of the map, which only your team can see. They’ll show up on your teammates’ map and minimap, and can be brought up in a number of ways.

  • Double-tapping the SNEAK key lets you quickly spot an enemy’s location.
  • When your intel is stolen a marker is dropped where it just was, shadowing its position and eliminating “I forgot where it was” situations.
  • Enemy positions are automatically revealed when the intel is captured, mimicking the old ‘radar’ reward.
  • Say !tunnel in teamchat to make an arrow where you’re standing, instruct other players to do something for a change with !build, or meet up with someone else using !here.

Players can type /clear to get rid of markers on their own screen. Admins can disable or enable placing of new markers using /togglemarkers. /togglemarkers <player> toggles only that player’s ability to do so.

Any functionality can be disabled switching off SHADOW_INTEL, REVEAL_ENEMIES, VV_ENABLED and CHAT_MARKERS below.

Commands
  • /clear clears all markers admin only
  • /togglemarkers <player> toggles a player’s ability to place markers admin only
  • /markers gives instructions

Code author: hompy

piqueserver.scripts.match script

Match script, useful for public matches. Features verbose announcements on IRC and a custom timer.

Commands
  • /timer starts timer admin only
  • /stoptimer stops timer admin only
  • /startrecord starts a fresh records admin only
  • /stoprecord clears the current record admin only
  • /saverecord save record to a file admin only

piqueserver.scripts.medkit script

Gives a specified amount of medkits on spawn

Commands
  • /medkit or /m utilizes available medkits to heal
Options
[medkit]
medkits = 1 # no. of medkits
medkit_heal_amount = 40 # how much hp. it gives

Code author: Booboorocks998 & mat^2

piqueserver.scripts.memcheck script

Runs the garbage collector at a given interval and displays any uncollected garbage found.

Code author: mat^2

piqueserver.scripts.minefield script

Minefield map extension.

Allows mappers to specify the map bounds, outside of which players will trip mines. Breaking blocks (when standing close to the field) also triggers a mine.

Warning

This script conflicts with smartnade script.

Commands
  • /minedebug toggles debug mode admin only

example extension from mapname.txt:

>>> extensions = {
...     'minefields' : [
...         #this minefield defines the border:
...         {
...             'border' : 1,
...             'left' : 59,
...             'top' : 154,
...             'right' : 451,
...             'bottom' : 355,
...         },
...         # this specifies an additional minefield (and shows a simpler
...         # syntax)
...         {
...             'area' : (183, 126, 224, 233),    #top left
...             'height' : 60 # this specifies until which block mines are
...                           # enabled (so you can build over it)
...         }
...     ]
... }

Code author: learn_more (MIT LICENSE)

piqueserver.scripts.nointelonwalls script

Prevents taking intel through walls

Version 2(2017.12.25)

Code author: kmsi<kmsiapps@gmail.com>

piqueserver.scripts.nospadingwalls script

piqueserver.scripts.paint script

Lets you change the color of the block you are looking at. With block tool selected, pick a color, then hold down sneak key (<V> by default) to paint.

Commands
  • /paint <player> enables painting mode for that player.

Code author: hompy

piqueserver.scripts.passreload script

Allows reloading config on the fly

Commands
  • /reloadconfig reloads the config (also updates rights) admin only

Code author: Danke

piqueserver.scripts.protect script

Protects areas against block destroying/building.

Commands
  • /protect <area coordinates> puts an area under protected status admin only
  • /protect clears all protected areas admin only

Code author: hompy

piqueserver.scripts.rampage script

Killing KILL_REQUIREMENT other players in under TIME_REQUIREMENT seconds sends you into a rampage lasting RAMPAGE_DURATION seconds. The rampage refills and drastically increases your weapons’ rate of fire.

By default this means 3 kills in under 8 seconds to activate. For reference, lines disappear from the killfeed 10 seconds after they appear.

Intended for use in frantic last team standing or free for all matches.

Code author: hompy

piqueserver.scripts.rangedamage script

Changes the damage values depending on distance.

Options
[rangedamange.rifle]
pct_per_block = 0 # percentage per block?
multiplier = 1

[rangedamange.smg]
pct_per_block = 0
multiplier = 1

[rangedamange.shotgun]
pct_per_block = 0
multiplier = 1

Code author: ?

piqueserver.scripts.rapid script

/rapid [player] will put the player in rapid mode, speeding up all tools including weapons.

RAPID_BLOCK_DELAY determines how fast block placement should be. Lowering this value is not recommended except for local use. Ping to the server is the effective lower limit: if this value is set to 0.1 (0.1 seconds = 100ms), users with ping above that won’t have the same advantage as the rest of the players.

Set ALWAYS_RAPID to TRUE to automatically get rapid when you login.

Code author: hompy

piqueserver.scripts.ratio script

Shows K/D ratio

Note

“ratio” must be AFTER “votekick” in the config script list

Commands
  • /ratio <player> shows K/D ratio

Code author: TheGrandmaster & mat^2

piqueserver.scripts.analyze script

Shows a detailed analysis of a players shots

Commands
  • /analyze or /an <target> to show a detailed analysis of a players shots if they hit someone. Shows hit player, distance, dT in milliseconds (Delta Time- Time since previous shot that hit someone. Useful for detecting multiple bullet or rapid hacks), weapon, which body part it hit, and a basic counter that displays the number of hits of that type.
  • /analyze or /an to disable it

Code author: a_girl

piqueserver.scripts.rollback script

Rollback rolls back the map to it’s original state by placing and removing changed blocks. This takes ages. Use with care.

Commands
  • /rollmap <map name> changes the map to the given map in a rolling fashion admin only
  • /rollmap <map name> <sector> changes the sector of the current map to the given map’s sector in a rolling fashion admin only
  • /rollback starts a rollback on the current map admin only
  • /rollbackcancel cancel an on-going rollback admin only

Warning

/rollmap will take a long time if number of differing blocks is too high.

Options
[rollback]
rollback_on_game_end = false

Code author: hompy

piqueserver.scripts.runningman script

Links any two teammates so that they explode if they stray away from each other. An incredibly unnerving method of encouraging teamplay.

Enable by doing /runningman or by setting ENABLED_AT_START = True Linking is automatic at spawn and tries to find new partners every two deaths. Use /unlink [player] to toggle yourself or someone from being linked.

May not work well with squads.

Code author: hompy

piqueserver.scripts.savemap script

Adds a /savemap command to save the current state of the map. You can specify any name with an argument, without it the map will be saved with the ‘.saved’ suffix. With /rmsaved you can delete a ‘.saved’ version of this map.

Options
[savemap]
# automatically load the saved map on map load
load_saved_map = true
# automatically save map at shutdown
save_at_shutdown = false
# automatically save map at map rotation or server shutdown
always_save_map = false

piqueserver.scripts.smartnade script

Smartnade script

Warning

This script conflicts with minefield script.

Code author: ?

piqueserver.scripts.spadenadefix script

Prevents spade-nade bug

version 2(2017.12.23)

Code author: kmsi<kmsiapps@gmail.com>

piqueserver.scripts.spawn_protect script

Protects spawned players for a specified amount of seconds.

Options
[spawn_protect]
protection_time = "3sec"

Code author: ? & kmsi <kmsiapps@gmail.com>

piqueserver.scripts.spectatorcontrol script

Lets you set restrictions on spectators.

Original documentation:

This script will hopefully give server owners some control over what spectators do on there server. As of now since the release of v0.75, Goon Haven has had issues with spectators idling and using global chat to send information to a team so that they may know enemy positions or what the enemy is doing, etc. This script can block spectator chat as well as kick spectators after so much time as passed.

Additionally, server owners who also give out “guard” or “mini-mod” positions can add the right “specpower” to the group rights in commands.py to have the guards/minimods be immune to the spectator kick and chat restrictions.

Oh, and admins are also automatically immune to spectator kick and chat restrictions.

Hope you enjoy! Tocksman

Options
[spectator_control]
no_chat = false # determines whether spectators can chat or not in your server
kick = false # determines whether spectators will be kicked after remaining for so long
kick_time = "5min" # how long a spectator may remain before they are kicked

Code author: Tocksman (made for Goon Haven)

piqueserver.scripts.squad script

BF-like squad system.

Commands
  • /squad <key> to join a squad
  • /follow <player> to spawn near a specific player
Options
[squad]
respawn_time = "10sec"
auto_squad = true

Code author: Triplefox

piqueserver.scripts.strongblock script

Blocks built by players are twice as hard to break.

  • You can remove your own blocks as if they weren’t strong.
  • Dirt-colored or buried blocks (those that turn into dirt) become normal blocks.

Code author: hompy

piqueserver.scripts.timedmute script

Allows muting players for a set amount of time.

Commands
  • /tm <player> <seconds> <reason> mute a player for set amount of time admin only

Note

Default time 5 minutes, default reason None

piqueserver.scripts.trusted script

Adds the ability to ‘trust’ certain players, i.e. they cannot be votekicked or rubberbanded.

Commands
  • /trust <player> gives trusted status to a player

Code author: mat^2 & hompy

piqueserver.scripts.votekick script

Allows users to start votekicks

Commands
  • /votekick <player> <reason> start votekick against a player
  • /y votes yes
  • /togglevotekick or /tvk toggles votekicks on/off globally
  • /togglevotekick or /tvk <player> toggles votekicks on/off for specific players
  • /cancel cancels a votekick
Options
[votekick]
# percentage of total number of players in the server required to vote to
# successfully votekick a player
percentage = 35

# duration that votekicked player will be banned for
ban_duration = "30min"

public_votes = true

Code author: James Hofmann a.k.a triplefox

piqueserver.scripts.votemap script

Allows players to vote for maps.

Commands
  • /votemap initiates map voting
  • /vote <map name> vote for a map
[votemap]
public_votes = true
extension_time = "15min"
player_driven = false
autoschedule = false
percentage = 80

Code author: James Hofmann a.k.a triplefox (GPL LICENSE)

piqueserver.scripts.welcome script

Greets specified people entering with messages

Options
[welcome]
welcomes = { nota = "Hi notafile", feik = "Hi feik" }

piqueserver.scripts.zoc script

Zones of control: Dropped intel and tents exert influence over nearby area, restricting player ability to destroy.

Options
[zoc]
radius = 32
attack_distance = 64
block_undo = 10
block_cost = 5
points_per_tick = 1
point_cap = 30
grenade_cost = 30

Included Game Modes

This is an Overview over the game modes that currently ship with piqueserver

Scripts

piqueserver.game_modes.arena gamemode

Arena, A game of team survival. The last team standing scores a point.

A map that uses arena needs to be modified to have a starting area for each team. A starting area is enclosed and has a gate on it. Each block of a gate must have the EXACT same color to work properly. Between each rounds, the gate is rebuilt. The gates are destroyed simultaneously at the start of each round, releasing the players onto the map. Players are free to switch weapons between rounds.

Spawn locations and gate locations MUST be present in the map metadata (map txt file) for arena to work properly.

The spawn location/s for the green team are set by using the data from the arena_green_spawns tuple in the extensions dictionary. Likewise, the blue spawn/s is set with the arena_blue_spawns key. arena_green_spawns and arena_blue_spawns are tuples which contain tuples of spawn coordinates. Spawn locations are chosen randomly.

Note

the script retains backwards compatibility With the old arena_green_spawn and arena_blue_spawn

The arena_max_spawn_distance can be used to set MAX_SPAWN_DISTANCE on a map by map basis. See the comment by MAX_SPAWN_DISTANCE for more information

The locations of gates is also determined in the map metadata. arena_gates is a tuple of coordinates in the extension dictionary. Each gate needs only one block to be specified (since each gate is made of a uniform color)

Sample extensions dictionary of an arena map with two gates: In this example there is one spawn location for blue and two spawn locations for green:

extensions = { 'arena': True, 'arena_blue_spawns' : ((128, 256, 60),),
'arena_green_spawns' : ((384, 256, 60), (123, 423, 51)), 'arena_gates':
((192, 236, 59), (320, 245, 60)) }

Code author: Yourself

piqueserver.game_modes.babel gamemode

Babel: reach the heavens by building a tower

Derived from onectf.py by Yourself

Release thread: http://www.buildandshoot.com/viewtopic.php?t=2586

piqueserver.game_modes.freeforall gamemode

Free for All: shoot anyone

Options

piqueserver.game_modes.infiltration gamemode

Infiltration is an asymetric intel-based game mode where one team, the attackers, tries to infiltrate the defenders base and steal the intel. Defenders receive points for keeping the intel out of the attackers hands and attackers recieve points for capturing the intel.

Options
[infiltration]
# Attackers get attacker_score_multiplier points for taking and capturing
# the intel.
attacker_score_multiplier = 10

# Defenders gain 1 point for every defender_score_interval seconds that the
# intel remains untouched.
defender_score_interval = "30sec"

# The ratio of attackers to defenders. Be aware that setting this
# incorrectly might prevent players from joining
attacker_ratio = 1.6

Originally created by: TheGrandmaster / hompy

piqueserver.game_modes.onectf gamemode

One CTF: CTF with a single intel, placed in the center.

piqueserver.game_modes.push gamemode

Code author: danhezee, StackOverflow, izzy, Danke, noway421, IAmYourFriend

The concept

Each team spawns at a set location with the enemy intel. They must “push” the intel towards their control point, which is also at a set location. The only way to arrive there is by building bridges over the deadly water. Further introduction to the game mode: https://youtu.be/DdisPY6vDD0

Setting Up New Maps

Spawn and CP locations must be configured via extensions in the map’s map_name.txt metadata:

>>> extensions = {
...     'push': True,
...     'push_spawn_range' : 5,
...     'push_blue_spawn' : (91, 276, 59),
...     'push_blue_cp' : (91, 276, 59),
...     'push_green_spawn' : (78, 86, 59),
...     'push_green_cp' : (78, 86, 59),
...     'water_damage' : 100
... }

Additional (but optional) extensions, to mark each team’s build area and prevent the enemy from building there (and thereby helping the enemy). The build area is defined by x and y of upper left corner, followed by x and y of bottom right corner on the map:

'push_blue_build_area' : (64, 100, 243, 500),
'push_green_build_area' : (268, 100, 447, 500),
Commands
  • /r Quickly respawn to refill blocks and ammo (if enabled)
  • /resetintel <team> Manually reset the blue or green team’s intel
Options
[push]
# Disallow removal of map blocks. This allows a larger variety of maps that
# rely on more fragile structures. It also prevents griefing (like removing
# the map blocks before and after your team's bridge).
protect_map_blocks = true

# Allow the usage of /r to quickly respawn. As players can't refill blocks at
# their base, they would have to suicide otherwise. This is illogical, messes
# up their kill-death ratio and gives them an undeserved punishing respawn time.
allow_respawn_command = true

# How long to wait to allow the command /r again
respawn_cmd_delay = "15sec"

# Players have to wait this amount after spawning before they can pick
# the intel up. This is to reduce the instant/careless intel pickups.
intel_pickup_delay = "3sec"

# How long can you remove your own last blocks
block_removal_delay = "15sec"

# Reset intel after it was dropped somewhere
reset_intel_after_drop = "3min"

# No building near cp within this block range (can be overwritten using
# map extension parameter "push_cp_protect_range")
default_cp_protect_range = 8

# Disable grenade damage within enemy spawn.
disable_grenades_at_spawn = false

piqueserver.game_modes.tdm gamemode

Team Deathmatch game mode.

Options

..Maintainer: Triplefox

piqueserver.game_modes.tow gamemode

Tug of War game mode, where you must progressively capture the enemy CPs in a straight line to win.

Maintainer: mat^2

Command line arguments

View of help output

$ piqueserver --help
usage: piqueserver [-h] [-c CONFIG_FILE] [-j JSON_PARAMETERS] [-d CONFIG_DIR]
                   [--copy-config] [--update-geoip]

piqueserver is an open-source Python server implementation for the voxel-based
game "Ace of Spades".

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config-file CONFIG_FILE
                        specify the config file - default is "config.json" in
                        the config dir
  -j JSON_PARAMETERS, --json-parameters JSON_PARAMETERS
                        add extra json parameters (overrides the ones present
                        in the config file)
  -d CONFIG_DIR, --config-dir CONFIG_DIR
                        specify the directory which contains maps, scripts,
                        etc (in correctly named subdirs) - default is
                        ~/.config/piqueserver
  --copy-config         copies the default/example config dir to its default
                        location or as specified by "-d"
  --update-geoip        download the latest geoip database

Explanation

-h or --help

self explanatory - display help about running

-c or --config-file

Takes a parameter which is the path to the desired configuration file. Defaults to config.json is the configuration directory.

-d or --config-dir

Specifies the directory to use for its configuration. Defaults to $XDG_CONFIG_HOME/piqueserver/ or $HOME/.config/piqueserver/ if the former environment variable isn’t set. This directory is also used by --copy-config as the target directory for copying the example configuration, as well as the base path when giving a relative path to --config-file.

--copy-config

Copies the included example configuration directory to the default configuration directory, or to the location specified by --config-dir. Will create the directory if it doesn’t exist, and will not copy if the directory already exists to avoid overwriting existing config.

-j or --json-parameters

Example: piqueserver -j '{"profile":true}'

Takes the json object and uses it to override fields from the json configuration file. Useful for testing out a quick change where you don’t want to edit config.json.

--update-geoip

Downloads the latest data file containing geoip data into data/GeoLiteCity.dat in the configuration directory. This data file is required for the from command to work in-game.

Running Piqueserver with Docker

Image

You can either use the published image on Docker Hub or build it locally.

# using published image
docker pull piqueserver/piqueserver:master
# build it locally
git clone https://github.com/piqueserver/piqueserver
cd piqueserver
docker build -t piqueserver/piqueserver:master .

Running

Get your config ready. It should have the following structure:

config/
├── config.toml
├── game_modes
│   └── README.md
├── logs
├── maps
│   ├── classicgen.txt
│   └── random.txt
├── README.md
└── scripts
    └── README.md

If you don’t have it you can grab it from the repository here. After setting up the config directory you can run piqueserver using the following command.

# running
docker run -d \
           -rm \
           -v /path/to/config:/config \
           -p 32887:32887/udp -p 32886:32886/tcp \
           --name mypiqueserver \
           piqueserver/piqueserver:master
# viewing logs
docker logs mypiqueserver
# killing the server
docker kill mypiqueserver

Note

-v flag only accepts absolute paths.
Ports 32887 and 32886 are for the game and status server respectively.
-p flag’s syntax is as follows host_port:container_port
If you want it to run on a different port change the host_port.

Docker Compose

Sample docker-compose file. You can find the full example here.

version: '3'
services:
arena:
    image: piqueserver/piqueserver:master
    volumes:
    - .:/config
    ports:
    - "8001:32887/udp" # game server
    - "8002:32886" # status server

Example Linux Setup with Systemd

Overview

These instructions will give you a flexible and secure setup for Piqueserver that starts automatically at boot, restarts on crashes, and collects logs.

It also allows you to run as many instances as you want in parallel.

Instructions

Install latest piqueserver using pip or whatever other method you like.

# pip3 install piqueserver

Create a dedicated directory for piqueserver data. You can put this anywhere you like. It is a good idea to put some identifier for your server, such as ctf in the folder name, in case you want to create more server configs in the future.

# mkdir -p /var/lib/piqueserver/servername/

We want a seperate group to be able to restrict permissions in a more granular way

# groupadd --system piqueserver

Optionally join your own user to the piqueserver group to be able to edit files in the piqueserver directory freely.

# usermod -a -G piqueserver yourusername

We want to copy the default config directory over.

# piqueserver --copy-config -d /var/lib/piqueserver/servername

Edit a new file, /etc/systemd/system/piqueserver@.service and insert the following contents.

[Unit]
Description=Piqueserver

[Service]
ExecStart=/usr/local/bin/piqueserver -d /var/lib/piqueserver/%i
User=piqueserver
Group=piqueserver
Restart=always

# Security Sandbox Settings
Group=piqueserver
DynamicUser=true
# only allow access to the state folder, nothing else
ProtectHome=true
TemporaryFileSystem=/var:ro
PrivateDevices=true
StateDirectory=piqueserver/%i

# disallow any unusual syscalls
SystemCallFilter=@system-service

[Install]
WantedBy=network.target

You can now start, stop, and see the status of the process using systemctl.

# systemctl start piqueserver@servername
# systemctl stop piqueserver@servername
# systemctl status piqueserver@servername

You will probably want to start the server at boot. To do this, run:

# systemctl enable piqueserver@servername

To tail the logs, run

# journalctl -f -u piqueserver@servername

Porting scripts

Piqueserver still supports scripting just as PySnip did! However, since Piqueserver has had much refactoring and improvements since forking from PySnip, some scripts that work with PySnip, may not work immediately with Piqueserver. Never fear! The piqueserver team has avoided breaking changes regarding scripts as much as possible, and here are some details on some points that could be breaking changes.

Tip

Do reach out to the piqueserver team we can help you out!

Automated port to Python 3

Since Piqueserver doesn’t support Python 2 anymore we have to port the scripts to Python 3. Most of the porting can be automated with tools like 2to3. We can later fix issues related to py2->py3 as they arise.

Fix feature_server module imports

# OLD
from map import ...
from commands import ...
from scheduler import ...
# NEW
from piqueserver.map import ...
from piqueserver.commands import ...
from piqueserver.scheduler import ...
from piqueserver.map import ...

map.DEFAULT_LOAD_DIR and other constants

This constant in feature_server/map.py is no longer, along with potentially others. Now, if you wish to get the map directory, you can use something like:

import os
from piqueserver.config import config
map_dir = os.path.join(config.config_dir, 'maps')

Fix packet imports

pyspades (and PySnip) have various packet instances in pyspades.server and those instances were used/shared by multiple scripts. Which can get messy and cause bugs. In piqueserver those instances are no longer present and scripts have to create those instances themselves.

# OLD
from pyspades.server import grenade_packet, block_action, set_color
# NEW
from pyspades.contained import GrenadePacket, BlockAction, SetColor
# lazy fix: package level global like below
grenade_packet, block_action, set_color = GrenadePacket(), BlockAction(), SetColor()
# proper fix: create them wherever they get used

Debugging import errors

Import errors in scripts causes piqueserver to throw NotImplementedError which is vague(sorry!). For debugging those import errors use python(shell) or ipython they’ll point you to which exact import is causing issues.

In [1]: import buildandsplat
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-01d8c693f582> in <module>
----> 1 import buildandsplat

~/piqueserver/piqueserver/game_modes/buildandsplat.py in <module>
    25 from pyspades.common import Vertex3, make_color, get_color
    26 from pyspades.constants import *
---> 27 from subprocess import add, admin, get_player, name
    28 from pyspades import contained as loaders
    29 from pyspades.weapon import WEAPONS

ImportError: cannot import name 'add' from 'subprocess' (/usr/local/lib/python3.7/subprocess.py)

Final

Try out the script and see if anything breaks. If the errors seem py2->py3 related refer to this cheatsheet. Piqueserver team has done a giant port of scripts in v0.1.1 it can be used as a reference. If you get stuck please reach out to the piqueserver team we are happy to help!

Contributing (developer)

This is the developer guide for contributing to piqueserver.

Requirements

Any code which does not follow the requirements below have chances of not being accepted into the master branch until modified to comply with them. You may submit code which does not comply with the requirements and leave for a maintainer to fix them, but it’s considered bad practice.

  • The EOL (End-of-line) character for the file types below must be LF (\n)
    • Python and Cython scripts (.py and .pyx)
    • C and C++ scripts (.c, .cpp, .h)
    • Shell scrips (.sh, .bash)
  • The EOL (End-of-line) character for the file types below must be CRLF (\r\n)
    • Windows scripts (.bat, .ps1)
  • All files must end with their respective EOL character, as mentioned above
  • Use autopep8 to format python source files.
  • Use clang-format to format C/C++ source files. We have a custom .clang-format file at the root of the repository.

Testing

Testing in piqueserver is performed with pytest with tox support.

Tox manages its own virtual environments, so if you have it installed on your system, testing is as simple as running tox, which will run all tests against all supported python versions (skipping those not available on your system).

If you already are in your virtual env and wish to test something quickly, pytest directly may be useful:

# make sure the venv is setup and all deps are installed
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt -r dev-requirements.txt

# build the cython extensions inplace
# otherwise pytest has issues
python setup.py build_ext --inplace

# and test away!
pytest

# single file
pytest tests/piqueserver/test_server.py

Code Coverage

Code coverage is generated with coverage.py using a pytest plugin.

# generate coverage data
pytest --cov=piqueserver --cov=pyspades

# build the report file
coverage html

# view at htmlcov/index.html

Work-flow recommendations

  • Get yourself comfortable with git from command-line, it allows you to do things that some GUIs can’t
  • Follow the below instructions for commit summaries (a.k.a. the first line)
  • No more than 70 characters of length
  • Describe an action, in imperative form
  • e.g.: implement anti-cheating optimizations
  • See also: Closing issues via commit messages

Finding history

Although piqueserver code is based on PySnip and does contain its commits, it’s not possible to see them by default in the GitHub web interface or gitk, due to a commit in the early days of piqueserver that made the imported files be treated as new ones.

To see the true history of a file, you must use an log visualizer that follows renames, such as:

  • gitk --follow *FILE*
  • git log --follow --pretty=oneline *OPTIONS*

Architecture

Overview

The piqueserver codebase is made up out of two main packages: pyspades and piqueserver. The piqueserver module used to be named feature_server. The original developers wanted to make the pyspades a generic AoS protocol and server implementation which the feature_server module then subclassed and specialized.

However, the unclear division and two locations quickly lead to a large mess, and it is currently not possible to know for certain which module exactly contains certain functionality. In general, this is the rule of thumb:

  • pyspades: Anything that involves sending and receiving of packets and acting on those, keeping game state.
  • piqueserver: Anything player-facing, for example commands, configuration, etc.

Note however the numerous exceptions to this. For example, parts of the command logic are in pyspades, while a lot of server validation logic is in piqueserver

Classes

There are currently two classes which contain the bulk of the logic. Connection, and Protocol. The Connection class does not actually represent a connection, it represents a player connected to the server and is contained in the player.py file in the relevant module. The Protocol object is similar, but represents the server and is in the server.py file.

Many classes and modules have descriptions you can view in this documentations or in the files themselves.

Extension Scripts

Piqueserver supports extension scripts aka “scripts” that modify it’s behaviour. The mechanism used to implement these is pretty ugly.

Each script defines an apply_script(protocol, connection, config) function. This function is called on script initialization with the protocol class, connection class and the config dict. Scripts are intended to then dynamically subclass the protocol and connection classes and return those, overriding methods where needed:

def apply_script(protocol, connection, config):
    class ScriptNameProtocol(protocol):
        def my_overridden_function(self, arg):
            ...
            return protocol.my_overridden_function(self, arg)
        ...

    class ScriptNameConnection(connection):
        ...

    return ScriptNameProtocol, ScriptNameConnection

The application of these scripts is performed in a loop, apply_script always being called with the classes returned by the last invocation. This way, the final class created inherits from all extension classes.

This is a terrible idea for a number of reasons, but just the way things work currently:

  • Extensions must meddle with the internals of piqueserver. This means that scripts are likely to break whenever any internals are changed, making substatial changes harder.
  • It is impossible to load or unload scripts after starting the server
  • There is no clear and defined API for scripts. This makes writing scripts unecessarily difficult and also increases breakage.
  • Debugging this is very hard

Game mode scripts are identical to regular extension scripts in functionality. However, they are required to define an attribute named game_mode on the Protocol class that describes the base game mode, CTF_MODE or TC_MODE. This is required because at a protocol level, AoS currently only supports those two game modes. Any other game modes must be emulated using the functionality provided by these two game modes.

Startup and Initialization

Startup and initialization of the server is currently poorly defined. The phases are roughly as follows:

Server Initialization

Config loading

The config and command-line arguments are loaded first, because they are required to proceed further.

Module initialization

The asyncio and twisted reactors are initialized. The files defining the game logic in piqueserver/ and pyspades/ are imported via piqueserver.server.

Script Loading

The way the script extension system works means that scripts can only be loaded before the two main classes, Connection and Protocol, are instantiated.

In this phase, the scripts defined in the config are loaded. This consists of four sub-phases:

  1. All scripts defined under scripts in the config are loaded, either from the config directory, or from sys.path.
  2. apply_script is called on all scripts, in the order that they are declared in the config.
  3. If the game mode is not "ctf" or "tc" for which the logic is built-in, the game mode is imported as a script.
  4. apply_script is called on the game mode script

Protocol Initialization

The Protocol class created in the previous step is instantiated, calling it’s __init__ method. This does the following:

  • Configure logging
  • Read configuration such as map rotation, team names, time limits, win conditions
  • Load the ban list
  • Import and initialize auxilliary functionality like SSH, status server, banpublish/bansubscribe, command console.
  • Initialize user configuration
  • Resolve the filenames of the map rotation
  • Create the enet.Host object, listening on the socket.
  • Create the Team and World objects.
  • Call protocol.on_advance(map_name) with the name of the map that is about to be loaded.
  • Schedule future tasks such as the update check, ban vaccum, map loading, annoucements, game end, update loop and master server connection.

Config Validation

The configuration is validated for any errors or unused keys.

Event Loop Startup

Control is passed to the Twisted/AsyncIO event loop, which drives everything from here on out.

Map Loading

The map is loaded or generated in a background thread. This is done to prevent connections from timing out on slow hardware.

Once complete, protocol.on_map_change, and Team.initialize() are called.

Map Change

Map change is triggered by protocol.advance_rotation

This:

  1. Calls protocol.on_advance with the next map.
  2. If a message was specified, print it and wait 10 seconds.
  3. Load or generate the next map.
  4. Call protocol.on_map_leave
  5. Call map_info.on_map_leave
  6. Replace the loaded map with the new one.
  7. Call Team.initialize

Game Mode Initialization

Games are defined by the game mode. They usually represent a period of time where two teams compete for points and end with one team winning or losing, or some other condition such as a time limit being hit.

There is currently no hook for when the game starts. However, existing modes assume it starts with the on_map_change call.

When a game ends, the game mode will call on_game_end to notify any other scripts and possibly trigger a map change.

Player Lifecycle

Players join and play the game in a number of phases and states:

Connection Start

An ENet connection handshake is performed. protocol.on_connect is called once complete.

This creates a new Connection object, on which connection.on_connect is called. This decides if the player should be allowed to join based on information such as the player count, IP or protocol version.

Map Transfer

The map is generated and sent to the player. A snapshot of current player state is saved, on top of which changes occurring during map transfer will be layered. A handshake is started to identify the client version and available protocol extensions.

On map transfer completion, connection.on_join will be called.

Limbo

After map transfer, players are in the “Limbo” state. They are in this state until the client sends an ExistingPlayer packet containing the chosen name and team ID.

Joined

Once a team is selected, connection.on_team_join is called to validate the team choice. connection.on_login is then called to validate the chosen name.

Spawned

When a player is about to be spawned, connection.on_spawn_location is called to allow overriding the position. connection.on_spawn is called when the spawn is performed.

Dead

When a player dies, connection.on_kill is called.

Release Guide

Ensure we’re ready for release

  • branch should be up to date
  • github action builds should be passing
  • test with a clean install on local machine if possible
  • all the following should run without error (assuming Linux machine):
# make sure we're clean and up to date
git checkout master
git pull
git clean -xffd
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt -r dev-requirements.txt

# build and install
python setup.py install
python setup.py build_ext --inplace

# run the tests
tox

# ensure the server is runnable
piqueserver -d piqueserver/config

Notes before continuing

These next steps involve pushing the packages to pypi as we go. Probably a safer workflow would be to build the packages first, then update Git with the version changes, and finally push to pypi. There is also the test pypi instance to experiment with uploading packages if required.

Most important to remember is the step where we edit some files to bump the version info.

Build the sdist

Prerequisites: Linux computer, python3.7 (or greater), pip (same version as the python you’re using), twine

The source distribution is OS agnostic, so this is the easiest to start with.

  • deactivate any venvs and ensure we’re back in a clean state on the correct branch:
git checkout master # or release branch if this is a bug fix on old supported version
git clean -xffd
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt -r dev-requirements.txt
  • IMPORTANT: update piqueserver/version.py with the new version numbers (this should be the single source of version info for piqueserver).
  • build and upload the source distribution:
python setup.py sdist
twine upload dist/*

Build Linux binary wheels

Prerequisites: same as for sdist, plus docker

  • make sure docker is running and you have a recent version of the pypa manylinux1 docker image
sudo docker pull quay.io/pypa/manylinux1_x86_64
  • build and upload the linux binary wheels:
./scripts/build_wheels.sh
twine upload wheelhouse/*

Build Windows binary wheels

Use the download-wheels tool to fetch the wheels from github actions.

python download_wheels.py --ghtoken <your github token> -d winbuilds/

Update git

Technically this can happen once we’re sure this will be a release, but if done after building the packages, at least we know it’s all fine.

  • add, commit, tag, and push the version changes:
git add -A
git commit -m "release version X"
git tag -a "v0.1.2"
git push
git push --tags
  • merge master across to the release branch if required:
git checkout v0.1.x
git merge master
git push
  • create and publish release notes on the pushed tag on GitHub

Celebrate!

🎉

piqueserver package

Module contents

Subpackages

piqueserver.core_commands package

Submodules
piqueserver.core_commands.game module
piqueserver.core_commands.game.fog(connection, *args)[source]

Set the fog color /fog red green blue (all values 0-255) /fog #aabbcc (hex representation of rgb) /fog #abc (short hex representation of rgb)

piqueserver.core_commands.game.get_time_limit(connection)[source]

Tell you the current time limit /time

piqueserver.core_commands.game.global_chat(connection, value=None)[source]

Enable or disable global chat /globalchat [on|off] Toggles if no arguments are given

piqueserver.core_commands.game.lock(connection, value)[source]

Make a specified team no longer joinable until it’s unlocked /lock <blue|green|spectator> New players will be placed in the spectator team even if it’s locked

piqueserver.core_commands.game.reset_game(connection)[source]

Reset the game /resetgame

piqueserver.core_commands.game.set_balance(connection, value)[source]

Turn automatic balancing on or off /setbalance <on|off>

piqueserver.core_commands.game.set_time_limit(connection, duration)[source]

Set this game time limit /timelimit <duration>

piqueserver.core_commands.game.switch(connection, player, team=None)[source]

Switch teams either for yourself or for a given player /switch [player] [team]

piqueserver.core_commands.game.toggle_build(connection, player=None)[source]

Toggle building for everyone in the server or for a given player /togglebuild [player]

piqueserver.core_commands.game.toggle_kill(connection, player=None)[source]

Toggle killing for everyone in the server or for a given player /togglekill [player]

piqueserver.core_commands.game.toggle_teamkill(connection)[source]

Toggle friendly fire /toggleteamkill

piqueserver.core_commands.game.unlock(connection, value)[source]

Unlock a team /unlock <blue|green|spectator>

piqueserver.core_commands.info module
piqueserver.core_commands.info.commands(connection)[source]

Print all available commands /commands

piqueserver.core_commands.info.help_command(connection, command_name=None)[source]

Gives description and usage info for a command /help <command_name>

piqueserver.core_commands.info.ping(connection, player)[source]

Tell your current ping (time for your actions to be received by the server) /ping

piqueserver.core_commands.info.rules(connection)[source]

Show you the server rules /rules

piqueserver.core_commands.info.streak(connection)[source]

Tell your current kill streak /streak

piqueserver.core_commands.map module
piqueserver.core_commands.map.advance(connection)[source]

Force the next map to be immediately loaded instead of waiting for the time limit to end /advancemap

piqueserver.core_commands.map.change_planned_map(connection, *pre_maps)[source]

Set the next map to be loaded after current game ends and inform everyone of it /map <mapname>

piqueserver.core_commands.map.change_rotation(connection, *pre_maps)[source]

Change the current map rotation and informs everyone on the server of it /rotation <map1> … <mapN>

piqueserver.core_commands.map.load_map(connection, map)[source]

Instantly switches map to the specified /loadmap <mapname>

piqueserver.core_commands.map.mapname(connection)[source]

Tell you what’s the name of the current map /mapname

piqueserver.core_commands.map.revert_rotation(connection)[source]

Invert the current map rotation /revertrotation

piqueserver.core_commands.map.rotation_add(connection, *pre_maps)[source]

Append a given map to the current map rotation and informs everyone on the server of it /rotationadd <map>

piqueserver.core_commands.map.show_rotation(connection)[source]

Tell you the current map rotation /showrotation

piqueserver.core_commands.moderation module
piqueserver.core_commands.moderation.ban(connection, value, *arg)[source]

Ban a given player forever or for a limited amount of time. /ban <player> [duration] [reason]

piqueserver.core_commands.moderation.banip(connection, ip, *arg)[source]

Ban an ip /banip <ip> [duration] [reason]

piqueserver.core_commands.moderation.dban(connection, value, *arg)[source]

Ban a given player for one day /dban <player> [reason]

piqueserver.core_commands.moderation.get_ban_arguments(connection, args)[source]

Parses duration and reason from arguments. It handles duration in two ways: interger mintues and human-friendly duration. It also handles cases where duration or reason are none. Note: It returns duration in seconds.

piqueserver.core_commands.moderation.god(connection, player=None)[source]

Go into god mode and inform everyone on the server of it /god [player]

piqueserver.core_commands.moderation.god_build(connection, player)[source]

Place blocks that can be destroyed only by players with godmode activated /godbuild [player]

piqueserver.core_commands.moderation.godsilent(connection, player=None)[source]

Silently go into god mode /godsilent [player]

piqueserver.core_commands.moderation.has_digits(s: str) → bool[source]
piqueserver.core_commands.moderation.hban(connection, value, *arg)[source]

Ban a given player for an hour /hban <player> [reason]

piqueserver.core_commands.moderation.invisible(connection, player)[source]

Turn invisible /invisible [player]

piqueserver.core_commands.moderation.ip(connection, player)[source]

Get the IP of a user /ip [player]

piqueserver.core_commands.moderation.kick(connection, value, *arg)[source]

Kick a given player /kick <player> Player is the #ID of the player, or an unique part of their name

piqueserver.core_commands.moderation.mute(connection, value)[source]

Mute a player /mute <player>

piqueserver.core_commands.moderation.pban(connection, value, *arg)[source]

Ban a given player permanently /pban <player> [reason]

piqueserver.core_commands.moderation.say(connection, *arg)[source]

Say something in chat as server message /say <text>

piqueserver.core_commands.moderation.unban(connection, ip)[source]

Unban an ip /unban <ip>

piqueserver.core_commands.moderation.undo_ban(connection, *arg)[source]

Undo last ban /undoban

piqueserver.core_commands.moderation.unmute(connection, value)[source]

Unmute a player /unmute <player>

piqueserver.core_commands.moderation.wban(connection, value, *arg)[source]

Ban a given player for one week /wban <player> [reason]

piqueserver.core_commands.moderation.who_was(connection, value)[source]

Get the IP of a user who has left /whowas <player>

piqueserver.core_commands.movement module
piqueserver.core_commands.movement.do_move(connection, args, silent=False)[source]
piqueserver.core_commands.movement.fly(connection, player)[source]

Enable flight /fly [player] Hold control and press space ;)

piqueserver.core_commands.movement.move(connection, *args)[source]

Move yourself or a given player to the specified x/y/z coordinates or sector /move [player] <sector> or /move [player] <x> <y> <z> If you’re invisible, it will happen silently. If the z coordinate makes the player appear underground, put them at ground level instead. If the x/y/z coordinate makes the player appear outside of the world bounds, take the bound instead

You can only move other players if you are admin or have the move_others right

piqueserver.core_commands.movement.move_silent(connection, *args)[source]

Silently move yourself or a given player to the specified x/y/z coordinates or sector /moves [player] <sector> or /moves [player] <x> <y> <z> If the z coordinate makes the player appear underground, put them at ground level instead. If the x/y/z coordinate makes the player appear outside of the world bounds, take the bound instead

You can only move other players if you are admin or have the move_others right

piqueserver.core_commands.movement.teleport(connection, player1, player2=None, silent=False)[source]

Teleport yourself or a given player to another player /teleport [player] <target player>

piqueserver.core_commands.movement.tpsilent(connection, player1, player2=None)[source]

Silently teleport a player to another player /tpsilent [player] <target player>

piqueserver.core_commands.movement.unstick(connection, player)[source]

Unstick yourself or another player and inform everyone on the server of it /unstick [player]

piqueserver.core_commands.movement.where(connection, player)[source]

Tell you the coordinates of yourself or of a given player /where [player]

piqueserver.core_commands.player module
piqueserver.core_commands.player.client(connection, player)[source]

Tell you information about your client or the client of a given player /client [player]

piqueserver.core_commands.player.deaf(connection, value=None)[source]

Make you or a given player no longer receive chat messages /deaf [player]

piqueserver.core_commands.player.heal(connection, player)[source]

Heal and refill yourself or a given player and inform everyone on the server of this action /heal [player]

piqueserver.core_commands.player.intel(connection)[source]

Inform you of who has the enemy intel /intel

piqueserver.core_commands.player.kill(connection, value=None)[source]

Kill yourself or a given player /kill [target]

piqueserver.core_commands.player.weapon(connection, value)[source]

Tell you what weapon a given player is using /weapon <player>

piqueserver.core_commands.server module
piqueserver.core_commands.server.scripts(connection)[source]

Tell you which scripts are enabled on this server currently /scripts

piqueserver.core_commands.server.server_info(connection)[source]

Tell you the name of this server and its aos:// URI /server

piqueserver.core_commands.server.server_name(connection, *arg)[source]

Change the server name until it restarts /servername <new-name> Also affects the master list

piqueserver.core_commands.server.toggle_master(connection)[source]

Toggle connection to the master server list /togglemaster

piqueserver.core_commands.server.version(connection)[source]

Tell you this server’s piqueserver version /version

piqueserver.core_commands.social module
piqueserver.core_commands.social.login(connection, password)[source]

Log in if you’re staff or a trusted member of this server /login <password> You will be kicked if a wrong password is given 3 times in a row

piqueserver.core_commands.social.pm(connection, value, *arg)[source]

Send a private message to a given player /pm <player> <message>

piqueserver.core_commands.social.to_admin(connection, *arg)[source]

Send a message to all admins currently online /admin <message>

Module contents

piqueserver.web package

Module contents

Submodules

piqueserver.banpublish module

class piqueserver.banpublish.PublishResource(factory)[source]

Bases: twisted.web.resource.Resource

getChild(name, request)[source]

Retrieve a ‘child’ resource from me.

Implement this to create dynamic resource generation – resources which are always available may be registered with self.putChild().

This will not be called if the class-level variable ‘isLeaf’ is set in your subclass; instead, the ‘postpath’ attribute of the request will be left as a list of the remaining path elements.

For example, the URL /foo/bar/baz will normally be:

| site.resource.getChild('foo').getChild('bar').getChild('baz').

However, if the resource returned by ‘bar’ has isLeaf set to true, then the getChild call will never be made on it.

Parameters and return value have the same meaning and requirements as those defined by L{IResource.getChildWithDefault}.

render_GET(request)[source]
class piqueserver.banpublish.PublishServer(protocol, port)[source]

Bases: object

update()[source]

piqueserver.bansubscribe module

class piqueserver.bansubscribe.BanManager(protocol)[source]

Bases: object

bans = None
fetch_filtered_bans(url: str, whitelist: List[str])[source]
get_ban(ip)[source]
start()[source]
update_bans()[source]
piqueserver.bansubscribe.validate_bansub_config(c)[source]

piqueserver.commands module

exception piqueserver.commands.CommandError[source]

Bases: Exception

class piqueserver.commands.CommandHelp(description, usage, info)

Bases: tuple

description

Alias for field number 0

info

Alias for field number 2

usage

Alias for field number 1

exception piqueserver.commands.PermissionDenied[source]

Bases: Exception

piqueserver.commands.add(func: Callable) → None[source]

Function to add a command from scripts. Deprecated

piqueserver.commands.add_rights(user_type: str, command_name: str) → None[source]

Give the user type a new right

>>> add_rights("knights", "say_ni")
piqueserver.commands.admin(func: Callable) → Callable[source]

Shorthand for @restrict(“admin”). Mainly exists for backwards compatibility with pyspades scripts.

>>> @admin
... @command()
... def some_command(x):
...     pass
piqueserver.commands.alias(name: str) → Callable[source]

add a new alias to a command. Deprecated

piqueserver.commands.command(name=None, *aliases, admin_only=False) → Callable[[Callable], Callable][source]

Register a new command.

The command will be accessible as /function_name unless a name or alias is specified.

>>> @command()
... def some_command(x):
...     pass

Optional names and aliases:

>>> @command("name", "alias1", "alias2")
... def some_command(x):
...     pass
piqueserver.commands.format_command_error(command_func: Callable, message: str, exception: Exception = None) → str[source]

format a help message for a given command

piqueserver.commands.get_command_help(command_func: Callable) → piqueserver.commands.CommandHelp[source]
piqueserver.commands.get_player(protocol, value: str, spectators=True)[source]

Get a player connection object by name or ID.

IDs are formatted as: “#12”

If no player with the specified name is found, it will try to find a player who’s name contains the string

piqueserver.commands.get_rights(user_type: str) → List[str][source]

Get a list of rights a specific user type has.

>>> add_rights("knights", "say_ni")
>>> get_rights("knights")
["say_ni"]
>>> get_rights("arthur")
[]
piqueserver.commands.get_team(connection, value)[source]
piqueserver.commands.get_truthy(value)[source]
piqueserver.commands.handle_command(connection, command, parameters)[source]

Public facing function to run a command, given the connection, a command name, and a list of parameters.

Will log the command.

piqueserver.commands.handle_input(connection, user_input)[source]
piqueserver.commands.has_permission(f, connection)[source]
piqueserver.commands.join_arguments(arg, default=None)[source]
piqueserver.commands.name(name: str) → Callable[source]

Give the command a new name. Deprecated

piqueserver.commands.parse_maps(pre_maps)[source]
piqueserver.commands.player_only(func: Callable)[source]

This recorator restricts a command to only be runnable by players connected to the server, not via other places such as, say, the console.

>>> @command()
... @player_only
... def some_command(x):
...     pass
piqueserver.commands.restrict(*user_types) → Callable[source]

restrict the command to only be used by a specific type of user

>>> @restrict("admin", "guard")
... @command()
... def some_command(x):
...     pass
piqueserver.commands.target_player(func: Callable)[source]
This decorator converts first argument of a command to a piqueserver.FeatureConnection.
It’s intended for commands which accept single argument for target player eg. /fly [player]. It implicitly uses invoker as target if no arguments are provided. It uses first argument are player name or id for targetting. It forces non-player invokers to provide player argument.
>>> @command()
... @target_player
... def fly(connection, target):
...     target.fly = True
...     pass
piqueserver.commands.update_rights(rights: Dict[KT, VT])[source]

Update the rights of all users according to the input dictionary. This is currently only here for when the config needs to be reloaded.

>>> update_rights({"knights": ["say_ni"]})
>>> get_rights("knights")
["say_ni"]

piqueserver.config module

class piqueserver.config.ConfigStore[source]

Bases: object

Configuration store that manages global configuration.

Usage example:

>>> config = ConfigStore()
>>> config.load_from_dict({'key1': 'value1'})
>>> option1 = config.option('key1', default='nothing',
...                         validate=lambda x: len(x) > 0)
>>> section1 = config.section('section1')
>>> nested_option = section1.option('key1', default=0)
>>> print(nested_option.get())
>>> option1.set('hello')
>>> # underlying dictionary structure will look like
>>> # {
>>> #   'key1': 'hello',
>>> #   'section1': {
>>> #     'key1': 0
>>> #   }
>>> # }
check_unused()[source]

Return the subset of the underlying dictionary that doesn’t have any corresponding registered options.

dump_to_file(fobj, format_='TOML')[source]

Writes the current configuration to a file-like objection, with the format specified by format_.

get_dict()[source]
load_from_dict(config)[source]

Load from a dictionary object directly.

load_from_file(fobj, format_='TOML')[source]

Clear the current configuration and load new configuration from a file-like object in a supported format.

option(name, default=None, cast=None, validate=None)[source]

Register and return a new option object.

section(name)[source]

Register and return a new section object.

update_from_dict(config)[source]

Load from a dictionary object directly.

update_from_file(fobj, format_='TOML')[source]

Updates the configuration from a file-like object. Useful for overwriting/updating part of the config without touching the rest.

piqueserver.config.cast_duration(d) → int[source]

casts duration(1min, 1hr) into seconds. If input is an int it returns that unmodified.

piqueserver.console module

class piqueserver.console.ConsoleInput(protocol)[source]

Bases: twisted.protocols.basic.LineReceiver

admin = True
delimiter = b'\n'
lineReceived(line)[source]

Override this for when each line is received.

@param line: The line which was received with the delimiter removed. @type line: C{bytes}

name = 'Console'
send_chat(value: str, _)[source]
send_lines(lines: List[str], type: str = None)[source]
piqueserver.console.create_console(protocol)[source]

piqueserver.irc module

class piqueserver.irc.IRCBot[source]

Bases: twisted.words.protocols.irc.IRCClient

admin
colors
irc_NICK(prefix, params)[source]

Called when a user changes their nickname.

irc_RPL_NAMREPLY(*arg)[source]
joined(irc_channel)[source]

Called when I finish joining a channel.

channel has the starting character (C{‘#’}, C{’&’}, C{‘!’}, or C{‘+’}) intact.

left(irc_channel)[source]

Called when I have left a channel.

channel has the starting character (C{‘#’}, C{’&’}, C{‘!’}, or C{‘+’}) intact.

me(msg, do_filter=False)[source]
modeChanged(user, irc_channel, *arg, **kw)[source]
name = None
nickname

str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

ops = None
privmsg(user, irc_channel, *arg, **kw)[source]
rights
send(msg, do_filter=False)[source]
send_chat(value: str, _)[source]
send_lines(lines: List[str], type: str = None)[source]
signedOn()[source]

Called after successfully signing on to the server.

unaliased_name = None
userKicked(kickee, irc_channel, kicker, message)[source]

Called when I observe someone else being kicked from a channel.

userLeft(user, irc_channel, *arg, **kw)[source]
userQuit(user, message)[source]

Called when I see another user disconnect from the network.

user_types
voices = None
class piqueserver.irc.IRCClientFactory(server, config)[source]

Bases: twisted.internet.protocol.ClientFactory

admin = None
aliases = None
bot = None
buildProtocol(address)[source]

Create an instance of a subclass of Protocol.

The returned instance will handle input on an incoming server connection, and an attribute “factory” pointing to the creating factory.

Alternatively, L{None} may be returned to immediately close the new connection.

Override this method to alter how Protocol instances get created.

@param addr: an object implementing L{IAddress}

clientConnectionFailed(connector, reason)[source]

Called when a connection has failed to connect.

It may be useful to call connector.connect() - this will reconnect.

@type reason: L{twisted.python.failure.Failure}

clientConnectionLost(connector, reason)[source]

Called when an established connection is lost.

It may be useful to call connector.connect() - this will reconnect.

@type reason: L{twisted.python.failure.Failure}

colors = True
failed_reconnect_delay = 60
lost_reconnect_delay = 20
protocol

alias of IRCBot

rights = None
startedConnecting(connector)[source]

Called when a connection has been started.

You can call connector.stopConnecting() to stop the connection attempt.

@param connector: a Connector object.

user_types = None
class piqueserver.irc.IRCRelay(protocol, config)[source]

Bases: object

factory = None
me(*arg, **kw)[source]
send(*arg, **kw)[source]
piqueserver.irc.alias(connection, value=None)[source]
piqueserver.irc.channel(func)[source]

This decorator rewrites the username of incoming messages to strip the ident and rejects it if the source channel is not equal to the channel the bot is in

piqueserver.irc.colors(connection)[source]
piqueserver.irc.format_name(player)[source]
piqueserver.irc.format_name_color(player)[source]
piqueserver.irc.score(connection)[source]
piqueserver.irc.unalias(connection)[source]
piqueserver.irc.who(connection)[source]

piqueserver.map module

class piqueserver.map.Map(rot_info: piqueserver.map.RotationInfo, load_dir: str)[source]

Bases: object

apply_script(protocol, connection, config)[source]
load_information(rot_info: piqueserver.map.RotationInfo, load_dir: str) → None[source]
load_vxl(rot_info)[source]
exception piqueserver.map.MapNotFound(the_map)[source]

Bases: Exception

class piqueserver.map.RotationInfo(name: str = 'pyspades')[source]

Bases: object

get_map_filename(load_dir: str) → str[source]
get_meta_filename(load_dir: str) → str[source]
get_seed() → int[source]
seed = None
piqueserver.map.check_rotation(maps: List[Union[str, RotationInfo]], load_dir: Optional[str] = None) → List[piqueserver.map.RotationInfo][source]

Checks if provided maps exist in maps dir. and returns an array of RotationInfo objects for those maps. Raises MapNotFound exception if maps are not found.

piqueserver.networkdict module

class piqueserver.networkdict.NetworkDict[source]

Bases: object

get_entry(key)[source]
iteritems()[source]
make_list()[source]
pop(*arg, **kw)[source]
read_list(values)[source]
remove(key)[source]

remove a key from the networkdict and return the removed items

piqueserver.networkdict.get_cidr(network)[source]

piqueserver.player module

class piqueserver.player.FeatureConnection(*args, **kwargs)[source]

Bases: pyspades.player.ServerConnection

ban(reason=None, duration=None)[source]
get_spawn_location() → Tuple[int, int, int][source]
kick(reason=None, silent=False)[source]
on_animation_update(jump: bool, crouch: bool, sneak: bool, sprint: bool) → Tuple[bool, bool, bool, bool][source]
on_block_build(x: int, y: int, z: int) → None[source]
on_block_build_attempt(x: int, y: int, z: int) → bool[source]
on_block_destroy(x: int, y: int, z: int, mode: int) → bool[source]
on_block_removed(x: int, y: int, z: int) → None[source]
on_chat(value: str, global_message: bool) → Union[str, bool][source]

notifies when the server receives a chat message

return False to block sending the message

on_command(command: str, parameters: List[str]) → None[source]
on_connect() → None[source]
on_disconnect() → None[source]
on_fall(damage: int) → Optional[bool][source]
on_grenade(time_left: float) → None[source]
on_hack_attempt(reason)[source]
on_hit(hit_amount: float, player: piqueserver.player.FeatureConnection, _type: int, grenade: pyspades.world.Grenade) → Optional[bool][source]
on_join() → None[source]
on_kill(killer: Optional[FeatureConnection], _type: int, grenade: None) → None[source]
on_line_build(points) → None[source]
on_line_build_attempt(points) → bool[source]
on_login(name: str) → None[source]
on_reset() → None[source]
on_team_join(team: pyspades.team.Team) → Optional[bool][source]
on_user_login(user_type, verbose=True)[source]
send_lines(lines: List[str], key: str = 'unknown') → None[source]

Send a list of lines to the player.

‘key’ is a unique identifier for the lines being sent - for example, a message saying ‘3 medkits are ready!’ could use the key ‘medkits.ready’. The key is used to avoid sending two messages of the same variety at once, to protect the server against a vulnerability which exploits this function.

The key should always be specified when calling this function. The default value of ‘unknown’ exists simply for backwards compatibility.

timed_out()[source]

piqueserver.run module

piqueserver.run.copy_config()[source]
piqueserver.run.copytree(src, dst)[source]

A re-implementation of shutil.copytree that doesn’t fail if dst already exists. Other properties: Doesn’t over-write if src/dst files don’t differ. Creates a backup of dst file before over-writing.

piqueserver.run.get_git_rev()[source]
piqueserver.run.main()[source]
piqueserver.run.update_geoip(target_dir)[source]

piqueserver.scheduler module

class piqueserver.scheduler.Scheduler(protocol)[source]

Bases: object

call_end(*arg, **kw)[source]
call_later(*arg, **kw)[source]
loop_call(delay, func, *arg, **kw)[source]
reset()[source]

piqueserver.server module

pyspades - default/featured server

class piqueserver.server.FeatureProtocol(interface: bytes, config_dict: Dict[str, Any])[source]

Bases: pyspades.server.ServerProtocol

add_ban(ip, reason, duration, name=None)[source]

Ban an ip with an optional reason and duration in seconds. If duration is None, ban is permanent.

advance_call = None
advance_rotation(message: Optional[str] = None) → twisted.internet.defer.Deferred[source]

Advances to the next map in the rotation. If message is provided it will send it to the chat, waits for 10 seconds and then advances.

Returns:Deferred that fires when the map has been loaded
balanced_teams = None
ban_manager = None
ban_publish = None
bans = None
broadcast_chat(value, global_message=True, sender=None, team=None, irc=False)[source]

Send a chat message to many users

building = True
call_end(delay: int, func: Callable, *arg, **kw) → piqueserver.utils._async.EndCall[source]
cancel_vote(connection=None)[source]
command_antispam = False
connectTCP(*arg, **kw)[source]
connection_class

alias of piqueserver.player.FeatureConnection

data_received(peer: enet.Peer, packet: enet.Packet) → None[source]
default_fog = (128, 232, 255)
everyone_is_admin = False
format(value: str, extra: Optional[Dict[str, str]] = None) → str[source]
format_lines(value: List[str]) → List[str][source]
game_mode = None
get_advance_time() → float[source]
get_external_ip(ip_getter: str) → Iterator[twisted.internet.defer.Deferred][source]
get_map_rotation()[source]
get_mode_name() → str[source]
global_chat = True
god_blocks = None
got_master_connection(client)[source]
identifier = None
interface = None
ip = None
irc_relay = None
irc_say(msg: str, me: bool = False) → None[source]
is_indestructable(x: int, y: int, z: int) → bool[source]
killing = True
last_time = None
listenTCP(*arg, **kw) → twisted.internet.tcp.Port[source]
make_map(rot_info: piqueserver.map.RotationInfo) → twisted.internet.defer.Deferred[source]

Creates and returns a Map object from rotation info in a new thread

Returns:Deferred that resolves to a Map object.
map_info = None
master = False
master_disconnected(client=None)[source]
master_reconnect_call = None
on_advance(map_name: str) → None[source]
on_ban(connection, reason, duration)[source]
on_ban_attempt(connection, reason, duration)[source]
on_game_end()[source]
on_map_change(the_map: pyspades.vxl.VXLData) → None[source]
on_map_leave()[source]
planned_map = None
player_memory = None
receive_callback(address: enet.Address, data: bytes) → int[source]

This hook receives the raw UDP data before it is processed by enet

reconnect_master()[source]
remote_console = None
remove_ban(ip)[source]
save_bans()[source]
send_chat(*args, **kwargs)[source]

Deprecated: see broadcast_chat

send_tip()[source]
set_map_name(rot_info: piqueserver.map.RotationInfo) → None[source]

Sets the map by its name.

set_map_rotation(maps: List[str]) → None[source]

Over-writes the current map rotation with provided one. FeatureProtocol.advance_rotation still needs to be called to actually change the map,

set_master_state(value)[source]
set_server_name(name: str) → None[source]
set_time_limit(time_limit: Optional[int] = None, additive: bool = False) → Optional[int][source]
shutdown()[source]

Notifies players and disconnects them before a shutdown.

spawns = None
team_class

alias of FeatureTeam

time_announce_schedule = None
timestamps = None
undo_last_ban()[source]
update_format() → None[source]

Called when the map (or other variables) have been updated

update_world()[source]
user_blocks = None
vacuum_bans()[source]

remove any bans that might have expired. This takes a while, so it is split up over the event loop

watch_for_releases()[source]

Starts a loop for check_for_releases and updates self.new_release.

class piqueserver.server.FeatureTeam(team_id: int, name: str, color: Tuple[int, int, int], spectator: bool, protocol: pyspades.protocol.BaseProtocol)[source]

Bases: pyspades.team.Team

get_entity_location(entity_id: int) → Tuple[int, int, int][source]
locked = False
piqueserver.server.ensure_dir_exists(filename: str) → None[source]
piqueserver.server.random_choice_cycle(choices)[source]
piqueserver.server.run() → None[source]

runs the server

piqueserver.server.sleep(secs)[source]
piqueserver.server.validate_team_name(name)[source]

piqueserver.ssh module

class piqueserver.ssh.RemoteConsole(server)[source]

Bases: object

piqueserver.ssh.create_remote_factory(namespace, users)[source]

piqueserver.statistics module

class piqueserver.statistics.StatsClient[source]

Bases: piqueserver.statistics.StatsProtocol

add_death(name)[source]
add_kill(name)[source]
connectionMade()[source]

Called when a connection is made.

This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

login_defers = None
login_user(name, password)[source]
object_received(obj)[source]
server = None
class piqueserver.statistics.StatsClientFactory(name, password, callback)[source]

Bases: twisted.internet.protocol.ReconnectingClientFactory

maxDelay = 20
protocol

alias of StatsClient

class piqueserver.statistics.StatsFactory(password)[source]

Bases: twisted.internet.protocol.ServerFactory

protocol

alias of StatsServer

class piqueserver.statistics.StatsProtocol[source]

Bases: twisted.protocols.basic.Int16StringReceiver

object_received(obj)[source]
send_object(obj)[source]
stringReceived(data)[source]

Override this for notification when each complete string is received.

@param string: The complete string which was received with all
framing (length prefix, etc) removed.

@type string: C{bytes}

class piqueserver.statistics.StatsServer[source]

Bases: piqueserver.statistics.StatsProtocol

add_death(name)[source]
add_kill(name)[source]
check_user(name, password)[source]
connectionLost(reason)[source]

Called when the connection is shut down.

Clear any circular references here, and any external references to this Protocol. The connection has been closed.

@type reason: L{twisted.python.failure.Failure}

connectionMade()[source]

Called when a connection is made.

This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

connection_accepted()[source]
object_received(obj)[source]
send_login_result(result)[source]
timed_out()[source]
piqueserver.statistics.connect_statistics(host, port, name, password, callback, interface='')[source]
piqueserver.statistics.hash_password(value)[source]

piqueserver.statusserver module

class piqueserver.statusserver.AccessLogger(logger: logging.Logger, log_format: str)[source]

Bases: aiohttp.abc.AbstractAccessLogger

log(request, response, time)[source]

Emit log to logger.

class piqueserver.statusserver.StatusServer(protocol)[source]

Bases: object

create_app()[source]
current_map
index(request)[source]
json(request)[source]
listen()[source]

Starts the status server on configured host/port

overview(request)[source]
update_cached_overview()[source]

Updates cached overview

piqueserver.statusserver.current_state(protocol)[source]

Gathers data on current server/game state from protocol class

piqueserver.statusserver.set_default_headers(request, response)[source]

piqueserver.version module

pyspades package

Module contents

pyspades - Ace of Spades network protocol implementations

Submodules

pyspades.bytes module

The ByteReader/Bytewriter classes are used to read and write various data types from and to byte-like objects. This is used e.g. to read the contents of packets.

class pyspades.bytes.ByteReader(input_data, int start=0, int size=-1)

Bases: object

Reads various data types from a bytes-like object

dataLeft(self) → int

get the number of bytes left in the buffer

Returns:The number of bytes left
Return type:int
read(self, int bytecount=-1)

read a number of bytes

Parameters:bytecount (int, optional) – The number of bytes to read. If omitted, all bytes available are read
Returns:bytecount bytes of data
Return type:bytes
readByte(self, bool unsigned=False) → int

read one byte of data as integer

Parameters:unsigned (bool) – If true, interpret the byte as unsigned
Returns:The value of the byte as int
Return type:int
readFloat(self, bool big_endian=True) → float

read four bytes of data as floating point number

Parameters:big_endian (bool, optional) – If true, interpret the bytes as big endian
Returns:The value of the bytes as float
Return type:float
readInt(self, bool unsigned=False, bool big_endian=True) → long long

read four bytes of data as integer

Parameters:
  • unsigned (bool) – If true, interpret the bytes as unsigned
  • big_endian (bool, optional) – If true, interpret the bytes as big endian
Returns:

The value of the bytes as int

Return type:

int

readReader(self, int size=-1) → ByteReader
readShort(self, bool unsigned=False, bool big_endian=True) → int

read two bytes of data as integer

Parameters:
  • unsigned (bool) – If true, interpret the bytes as unsigned
  • big_endian (bool, optional) – If true, interpret the bytes as big endian
Returns:

The value of the bytes as int

Return type:

int

readString(self, int size=-1) → bytes

read a string

Parameters:size (int) – If set, read size bytes, else read all bytes available
Returns:The value of the bytes
Return type:bytes
rewind(self, int value)

move the position bytecount bytes back

Parameters:bytecount – number of bytes to move back
seek(self, size_t pos)

move to a position in the buffer

Parameters:pos (int) – position to seek to
skipBytes(self, int bytecount)

move the position bytecount bytes ahead

Parameters:bytecount – number of bytes to move ahead
tell(self) → size_t

get the current position in the buffer

Returns:The current position in bytes
Return type:int
class pyspades.bytes.ByteWriter

Bases: object

pad(self, int bytecount)
rewind(self, int bytecount)
tell(self) → size_t
write(self, data)
writeByte(self, int value, bool unsigned=False)
writeFloat(self, float value, bool big_endian=True)
writeInt(self, long long value, bool unsigned=False, bool big_endian=True)
writeShort(self, int value, bool unsigned=False, bool big_endian=True)
writeString(self, value, int size=-1)
writeStringSize(self, char *value, int size)
exception pyspades.bytes.NoDataLeft

Bases: Exception

pyspades.collision module

pyspades.collision.collision_3d(x1, y1, z1, x2, y2, z2, distance=3)[source]
pyspades.collision.distance_3d(xyz1, xyz2)[source]
pyspades.collision.distance_3d_vector(vector1, vector2)[source]
pyspades.collision.vector_collision(vec1, vec2, distance=3)[source]

pyspades.color module

pyspades.color.hsb_to_rgb(hue: float, sat: float, bri: float) → Tuple[float, float, float][source]
pyspades.color.interpolate_hsb(xyz1: Tuple[float, float, float], xyz2: Tuple[float, float, float], t: float) → Tuple[float, float, float][source]
pyspades.color.interpolate_rgb(xyz1, xyz2, t)[source]
pyspades.color.rgb_distance(xyz1, xyz2)[source]
pyspades.color.wrap(minimum: float, maximum: float, value: float) → float[source]

pyspades.common module

class pyspades.common.Quaternion(*arg)

Bases: object

copy(self)
get(self)
get_matrix(self)
inverse_transform_vector(self, Vertex3 v) → Vertex3
multiply_scalar(self, double k)
nlerp(self, Quaternion q, double t) → Quaternion
normalize(self)
set(self, double w, double x, double y, double z)
set_angle_axis(self, double radians, Vertex3 axis) → Quaternion
slerp(self, Quaternion q, double t) → Quaternion
transform_vector(self, Vertex3 v) → Vertex3
w

‘double’

Type:w
x

‘double’

Type:x
y

‘double’

Type:y
z

‘double’

Type:z
class pyspades.common.Vertex3(is_ref=False, *arg)

Bases: object

copy(self)
cross(self, Vertex3 A)
distance(self, Vertex3 other)

calculate euclidean (straight line) distance between two Vertex3 as points in 3d space. Equivalent to (a - b).length().

dot(self, Vertex3 A)
get(self)
get_rotation_to(self, Vertex3 A) → Quaternion
is_zero(self)
length(self)
length_sqr(self)

calculate the square length of the vertex3. This is a bit faster than getting the length, as it avoids the expensive sqrt call.

normal(self)
normalize(self)
perp_dot(self, Vertex3 A)
rotate(self, Vertex3 A)
set(self, float x, float y, float z)
set_vector(self, Vertex3 vector)
translate(self, double x, double y, double z)
unrotate(self, Vertex3 A)
x
y
z
zero(self)
pyspades.common.coordinates(data)
pyspades.common.crc32(data)
pyspades.common.decode(value)
pyspades.common.encode(value)
pyspades.common.escape_control_codes(untrusted_str)

escape all ascii control codes in a string note: this still leaves things like special unicode characters in place

pyspades.common.get_color(color)
pyspades.common.make_color(r, g, b)
pyspades.common.prettify_timespan(total, get_seconds=False)
pyspades.common.to_coordinates(x, y)

pyspades.constants module

pyspades.contained module

This module contains the definitions and registrations for the various packets used in the server

class pyspades.contained.BlockAction

Bases: pyspades.loaders.Loader

id = 13
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
value

‘int’

Type:value
write(self, ByteWriter writer)
x

‘int’

Type:x
y

‘int’

Type:y
z

‘int’

Type:z
class pyspades.contained.BlockLine

Bases: pyspades.loaders.Loader

id = 14
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
write(self, ByteWriter writer)
x1

‘int’

Type:x1
x2

‘int’

Type:x2
y1

‘int’

Type:y1
y2

‘int’

Type:y2
z1

‘int’

Type:z1
z2

‘int’

Type:z2
class pyspades.contained.CTFState

Bases: pyspades.loaders.Loader

cap_limit

‘unsigned int’

Type:cap_limit
id = 0
read(self, ByteReader reader)
team1_base_x

‘float’

Type:team1_base_x
team1_base_y

‘float’

Type:team1_base_y
team1_base_z

‘float’

Type:team1_base_z
team1_carrier

‘unsigned int’

Type:team1_carrier
team1_flag_x

‘float’

Type:team1_flag_x
team1_flag_y

‘float’

Type:team1_flag_y
team1_flag_z

‘float’

Type:team1_flag_z
team1_has_intel

‘bool’

Type:team1_has_intel
team1_score

‘unsigned int’

Type:team1_score
team2_base_x

‘float’

Type:team2_base_x
team2_base_y

‘float’

Type:team2_base_y
team2_base_z

‘float’

Type:team2_base_z
team2_carrier

‘unsigned int’

Type:team2_carrier
team2_flag_x

‘float’

Type:team2_flag_x
team2_flag_y

‘float’

Type:team2_flag_y
team2_flag_z

‘float’

Type:team2_flag_z
team2_has_intel

‘bool’

Type:team2_has_intel
team2_score

‘unsigned int’

Type:team2_score
write(self, ByteWriter writer)
class pyspades.contained.ChangeTeam

Bases: pyspades.loaders.Loader

id = 29
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
team

‘int’

Type:team
write(self, ByteWriter writer)
class pyspades.contained.ChangeWeapon

Bases: pyspades.loaders.Loader

id = 30
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
weapon

‘int’

Type:weapon
write(self, ByteWriter writer)
class pyspades.contained.ChatMessage

Bases: pyspades.loaders.Loader

chat_type

‘unsigned int’

Type:chat_type
id = 17
player_id

‘unsigned int’

Type:player_id
read(self, ByteReader reader)
value

object

Type:value
write(self, ByteWriter writer)
class pyspades.contained.CreatePlayer

Bases: pyspades.loaders.Loader

id = 12
name

object

Type:name
player_id

‘unsigned int’

Type:player_id
read(self, ByteReader reader)
team

‘int’

Type:team
weapon

‘unsigned int’

Type:weapon
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.ExistingPlayer

Bases: pyspades.loaders.Loader

color

‘unsigned int’

Type:color
id = 9
kills

‘int’

Type:kills
name

object

Type:name
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
team

‘int’

Type:team
tool

‘int’

Type:tool
weapon

‘int’

Type:weapon
write(self, ByteWriter writer)
class pyspades.contained.FogColor

Bases: pyspades.loaders.Loader

color

‘int’

Type:color
id = 27
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.GrenadePacket

Bases: pyspades.loaders.Loader

id = 6
player_id

‘int’

Type:player_id
position

tuple

Type:position
read(self, ByteReader reader)
value

‘float’

Type:value
velocity

tuple

Type:velocity
write(self, ByteWriter writer)
class pyspades.contained.HandShakeInit

Bases: pyspades.loaders.Loader

id = 31
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.HandShakeReturn

Bases: pyspades.loaders.Loader

id = 32
read(self, ByteReader reader)
success

‘int’

Type:success
write(self, ByteWriter writer)
class pyspades.contained.HitPacket

Bases: pyspades.loaders.Loader

id = 5
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
value

‘int’

Type:value
write(self, ByteWriter writer)
class pyspades.contained.InputData

Bases: pyspades.loaders.Loader

crouch

‘bool’

Type:crouch
down

‘bool’

Type:down
id = 3
jump

‘bool’

Type:jump
left

‘bool’

Type:left
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
right

‘bool’

Type:right
sneak

‘bool’

Type:sneak
sprint

‘bool’

Type:sprint
up

‘bool’

Type:up
write(self, ByteWriter writer)
class pyspades.contained.IntelCapture

Bases: pyspades.loaders.Loader

id = 23
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
winning

‘bool’

Type:winning
write(self, ByteWriter writer)
class pyspades.contained.IntelDrop

Bases: pyspades.loaders.Loader

id = 25
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.IntelPickup

Bases: pyspades.loaders.Loader

id = 24
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.KillAction

Bases: pyspades.loaders.Loader

id = 16
kill_type

‘int’

Type:kill_type
killer_id

‘int’

Type:killer_id
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
respawn_time

‘int’

Type:respawn_time
write(self, ByteWriter writer)
class pyspades.contained.MapChunk

Bases: pyspades.loaders.Loader

data

object

Type:data
id = 19
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.MapStart

Bases: pyspades.loaders.Loader

id = 18
read(self, ByteReader reader)
size

‘unsigned int’

Type:size
write(self, ByteWriter writer)
class pyspades.contained.MoveObject

Bases: pyspades.loaders.Loader

id = 11
object_type

‘unsigned int’

Type:object_type
read(self, ByteReader reader)
state

‘unsigned int’

Type:state
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.ObjectTerritory

Bases: pyspades.loaders.Loader

item

object

Type:item
write(self, ByteWriter writer)
class pyspades.contained.OrientationData

Bases: pyspades.loaders.Loader

id = 1
read(self, ByteReader reader)
set(self, pos)
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.PlayerLeft

Bases: pyspades.loaders.Loader

id = 20
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.PositionData

Bases: pyspades.loaders.Loader

id = 0
read(self, ByteReader reader)
set(self, pos)
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.ProgressBar

Bases: pyspades.loaders.Loader

capturing_team

‘unsigned int’

Type:capturing_team
id = 22
object_index

‘unsigned int’

Type:object_index
progress

‘float’

Type:progress
rate

‘int’

Type:rate
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.ProtocolExtensionInfo

Bases: pyspades.loaders.Loader

packet used to exchange the list of supported protocol extensions between server and client

extensions is a list of (extension_id, version) tuples

extensions

list

Type:extensions
id = 60
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.Restock

Bases: pyspades.loaders.Loader

id = 26
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.SetColor

Bases: pyspades.loaders.Loader

id = 8
player_id

‘unsigned int’

Type:player_id
read(self, ByteReader reader)
value

‘unsigned int’

Type:value
write(self, ByteWriter writer)
class pyspades.contained.SetHP

Bases: pyspades.loaders.Loader

hp

‘int’

Type:hp
id = 5
not_fall

‘int’

Type:not_fall
read(self, ByteReader reader)
source_x

‘float’

Type:source_x
source_y

‘float’

Type:source_y
source_z

‘float’

Type:source_z
write(self, ByteWriter writer)
class pyspades.contained.SetTool

Bases: pyspades.loaders.Loader

id = 7
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
value

‘int’

Type:value
write(self, ByteWriter writer)
class pyspades.contained.ShortPlayerData

Bases: pyspades.loaders.Loader

id = 10
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
team

‘int’

Type:team
weapon

‘int’

Type:weapon
write(self, ByteWriter writer)
class pyspades.contained.StateData

Bases: pyspades.loaders.Loader

fog_color

tuple

Type:fog_color
id = 15
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
state

pyspades.loaders.Loader

Type:state
team1_color

tuple

Type:team1_color
team1_name

object

Type:team1_name
team2_color

tuple

Type:team2_color
team2_name

object

Type:team2_name
write(self, ByteWriter writer)
class pyspades.contained.TCState

Bases: pyspades.loaders.Loader

id = 1
read(self, ByteReader reader)
set_entities(self, items)
territories

list

Type:territories
write(self, ByteWriter writer)
class pyspades.contained.Territory

Bases: pyspades.loaders.Loader

read(self, ByteReader reader)
state

‘unsigned int’

Type:state
write(self, ByteWriter writer)
x

‘float’

Type:x
y

‘float’

Type:y
z

‘float’

Type:z
class pyspades.contained.TerritoryCapture

Bases: pyspades.loaders.Loader

id = 21
object_index

‘unsigned int’

Type:object_index
read(self, ByteReader reader)
state

‘unsigned int’

Type:state
winning

‘unsigned int’

Type:winning
write(self, ByteWriter writer)
class pyspades.contained.VersionRequest

Bases: pyspades.loaders.Loader

id = 33
read(self, ByteReader reader)
write(self, ByteWriter writer)
class pyspades.contained.VersionResponse

Bases: pyspades.loaders.Loader

client

unicode

Type:client
id = 34
os_info

unicode

Type:os_info
read(self, ByteReader reader)
version

tuple

Type:version
write(self, ByteWriter writer)
class pyspades.contained.WeaponInput

Bases: pyspades.loaders.Loader

id = 4
player_id

‘int’

Type:player_id
primary

‘bool’

Type:primary
read(self, ByteReader reader)
secondary

‘bool’

Type:secondary
write(self, ByteWriter writer)
class pyspades.contained.WeaponReload

Bases: pyspades.loaders.Loader

clip_ammo

‘int’

Type:clip_ammo
id = 28
player_id

‘int’

Type:player_id
read(self, ByteReader reader)
reserve_ammo

‘int’

Type:reserve_ammo
write(self, ByteWriter writer)
class pyspades.contained.WorldUpdate

Bases: pyspades.loaders.Loader

id = 2
items

list

Type:items
read(self, ByteReader reader)
write(self, ByteWriter writer)

pyspades.entities module

class pyspades.entities.Base(entity_id, protocol, *arg, **kw)[source]

Bases: pyspades.entities.Entity

class pyspades.entities.Entity(entity_id, protocol, *arg, **kw)[source]

Bases: pyspades.common.Vertex3

team = None
update()[source]
class pyspades.entities.Flag(entity_id, protocol, *arg, **kw)[source]

Bases: pyspades.entities.Entity

player = None
update()[source]
class pyspades.entities.Territory(*arg, **kw)[source]

Bases: pyspades.entities.Flag

add_player(player)[source]
capturing_team = None
finish()[source]
finish_call = None
get_progress(set=False)[source]

Return progress (between 0 and 1 - 0 is full blue control, 1 is full green control) and optionally set the current progress.

get_spawn_location()[source]
players = None
progress = 0.0
rate = 0
rate_value = 0.0
remove_player(player)[source]
send_progress()[source]
start = None
update_rate()[source]

pyspades.loaders module

class pyspades.loaders.Loader(ByteReader reader=None)

Bases: object

generate(self) → ByteWriter
read(self, ByteReader reader)
write(self, ByteWriter writer)

pyspades.mapgenerator module

The map generator is responsible for generating the map bytes that get sent to the client on connect

class pyspades.mapgenerator.MapGeneratorChild(generator)[source]

Bases: object

data_left()[source]

return True if any data is left

get_size()[source]

get the size of the parent map generator

pos = 0
read(size)[source]

read size bytes from the parent map generator, if possible

class pyspades.mapgenerator.ProgressiveMapGenerator(map_, parent=False)[source]

Bases: object

Progressively generates the stream of bytes sent to the client for map downloads.

It supports two modes. In the default parent=False mode, reading is normal.

In the parent=True mode a child generator is created with get_child to actually read the data. This is presumably done so that the work of map generation is not duplicated for each client if several connect at the same time.

all_data = b''
data = b''
data_left()[source]

return True if any data is left

done = False
get_child()[source]

return a new child generator

get_size()[source]

get the map size, for display of the loading bar on the client

pos = 0
read(size)[source]

read size bytes from the map generator

pyspades.mapmaker module

class pyspades.mapmaker.Biome

Bases: object

class pyspades.mapmaker.BiomeMap(biomes, width=32, height=32)

Bases: object

A tilemap containing biome data for a HeightMap.

biomes

list

Type:biomes
create_heightmap(self)

Return a HeightMap with unfinished color data and a list of gradients. When finished with post-processing, use hmap.rewrite_gradient_fill(gradients).

get_repeat(self, int x, int y)

This allows the algorithm to tile at the edges.

gradients

list

Type:gradients
height

‘int’

Type:height
jitter(self)
noise(self)
point_flood(self, points)

Each tuple of (x,y,biome) in the “points” list is round-robined through a flooding algorithm. The algorithm uses one queue for each flood, so that the flooding is as even as possible.

random_points(self, qty, biome, x=0, y=0, w=None, h=None)

Generate some points for point_flood()

rect_of_point(self, x, y)
set_repeat(self, int x, int y, val)

This allows the algorithm to tile at the edges.

theight

‘int’

Type:theight
tmap

list

Type:tmap
twidth

‘int’

Type:twidth
width

‘int’

Type:width
class pyspades.mapmaker.Gradient

Bases: object

array
hsb

Linear interpolation of (0-360,0-100,0-100) HSB values as used in GIMP.

rgb

Linear interpolation of (0-255) RGB values.

set_step_hsb
set_step_rgb
class pyspades.mapmaker.HeightMap(height)

Bases: object

add_repeat(self, int x, int y, double val)
blend_heightmaps(self, HeightMap alphamap, HeightMap HeightMap)

Blend according to two HeightMaps: one as an alpha-mask, the other contains desired heights

cmap

object

Type:cmap
dipping(self)

Adds a “dipping” feel to the map.

fill_col(self, int col)
get(self, int x, int y) → double
get_col(self, int x, int y) → int
get_col_repeat(self, int x, int y) → int
get_repeat(self, int x, int y) → double

This allows the algorithm to tile at the edges.

height

‘int’

Type:height
hmap

object

Type:hmap
jitter_colors(self, double amount)

Image jittering filter. Amount is max pixels distance to jitter.

jitter_heights(self, double amount)

Image jittering filter. Amount is max pixels distance to jitter.

level_against_heightmap(self, HeightMap other, double height)

Use another HeightMap as an alpha-mask to force values to a specific height

line_add(self, int x, int y, int x2, int y2, int radius, double depth)
line_set(self, int x, int y, int x2, int y2, int radius, double height)
midpoint_displace(self, double jittervalue, double spanscalingmultiplier, int skip=0)

Midpoint displacement with the diamond-square algorithm.

mult_repeat(self, int x, int y, double mult)
offset_z(self, double qty)
paint_gradient_fill(self, gradient)

Surface the map with a single gradient.

peaking(self)

Adds a “peaking” feel to the map.

rect_color(self, int x, int y, int w, int h, int col)
rect_noise(self, int x, int y, int w, int h, double jitter, double midpoint)
rect_solid(self, int x, int y, int w, int h, double z)
rescale_z(self, double multiple)
rewrite_gradient_fill(self, list gradients)

Given a cmap of int-indexed gradient definitions, rewrite them as surface color definitions.

rgb_noise_colors(self, low, high)

Add noise to the heightmap colors.

rolling(self)

Adds a “rolling” feel to the map.

seed(self, double jitter, double midpoint)
set(self, int x, int y, double val)
set_col_repeat(self, int x, int y, int val)
set_repeat(self, int x, int y, double val)

This allows the algorithm to tile at the edges.

smooth_colors(self)

Average the color of each pixel to add smoothness.

smoothing(self)

Does some simple averaging to bring down the noise level.

truncate(self)

Truncates the HeightMap to a valid (0-1) range. Do this before painting or writing to voxels to avoid crashing.

width

‘int’

Type:width
write_vxl(self)
pyspades.mapmaker.generate_classic(seed)
pyspades.mapmaker.get_b(int color) → int
pyspades.mapmaker.get_g(int color) → int
pyspades.mapmaker.get_r(int color) → int
pyspades.mapmaker.make_color(int r, int g, int b) → int

pyspades.master module

Implementation of the 0,75 master server protocol

class pyspades.master.AddServer[source]

Bases: pyspades.loaders.Loader

The AddServer packet sent to the master server

count
game_mode
id = 4
map
max_players
name
port
read(self, ByteReader reader)[source]
write(self, ByteWriter writer)[source]
class pyspades.master.MasterConnection(protocol, peer)[source]

Bases: pyspades.protocol.BaseConnection

connected = False
disconnect_callback = None
on_connect()[source]
on_disconnect()[source]
send_server()[source]
set_count(value)[source]
pyspades.master.get_master_connection(protocol)[source]

pyspades.packet module

pyspades.packet.call_packet_handler(self, loader)
pyspades.packet.load_client_packet(data)
pyspades.packet.load_server_packet(data)
pyspades.packet.register_packet(loader=None, server=True, client=True, extension=None)

register a packet

>>> @register_packet()
... class SomePacket(Loader):
...     pass

Optionally can be used as function too, but this is discouraged unless you need to do this (the default pyspades loaders need this, as they are defined in C++ code):

>>> class AnotherPacket(Loader):
...     pass
...
>>> register_packet(AnotherPacket, server=False)

Additionally you can specify if only the server or client can send this packet. This is only useful in a few rare cases, so these are set to True by default.

Parameters:
  • server (bool, optional) – This packet can be sent by the server. True by default.
  • client (bool, optional) – This packet can be sent by the client. True by default
  • extension (int, optional) – The extension id this packet belongs to, if any
Raises:

KeyError – If the packet’s ID has already been registered

pyspades.packet.register_packet_handler(loader)

pyspades.player module

class pyspades.player.ServerConnection(*arg, **kw)[source]

Bases: pyspades.protocol.BaseConnection

add_score(score)[source]
address = None
blocks = None
capture_flag()[source]
check_refill()[source]
check_speedhack(x: float, y: float, z: float, distance: None = None) → bool[source]
client_string
color = (112, 112, 112)
continue_map_transfer() → None[source]
drop_flag() → None[source]
filter_animation_data = False
filter_visibility_data = False
filter_weapon_input = False
freeze_animation = False
get_location()[source]
get_respawn_time() → float[source]
get_spawn_location() → Tuple[int, int, int][source]
grenade_exploded(grenade: pyspades.world.Grenade) → None[source]
grenades = None
hit(value, by=None, kill_type=0)[source]
hp = None
is_location_free(x, y, z)[source]
is_valid_position(x: float, y: float, z: float, distance: None = None) → bool
kill(by: None = None, kill_type: int = 0, grenade: None = None) → None[source]
kills = 0
last_block = None
last_block_destroy = None
last_position_update = None
last_refill = None
loader_received(loader: enet.Packet) → None[source]

called when a loader i.e. packet is received. calls the packet handler registered with @register_packet_handler

local = False
map_data = None
map_packets_sent = 0
name = None
on_animation_update(jump, crouch, sneak, sprint)[source]
on_block_action_recieved(contained: pyspades.contained.BlockAction) → None[source]
on_block_build(x, y, z)[source]
on_block_build_attempt(x, y, z)[source]
on_block_destroy(x, y, z, mode)[source]
on_block_line_recieved(contained)[source]
on_block_removed(x, y, z)[source]
on_chat(value, global_message)[source]
on_chat_message_recieved(contained: pyspades.contained.ChatMessage) → None[source]
on_chat_sent(value: str, global_message: bool) → None[source]
on_color_change_recieved(contained: pyspades.contained.SetColor) → None[source]
on_color_set(color: Tuple[int, int, int]) → None[source]
on_color_set_attempt(color: Tuple[int, int, int]) → None[source]
on_command(command, parameters)[source]
on_connect() → None[source]
on_disconnect() → None[source]
on_ext_info_received(contained: pyspades.contained.ProtocolExtensionInfo) → None[source]
on_fall(damage)[source]
on_flag_capture()[source]
on_flag_drop()[source]
on_flag_take()[source]
on_fog_color_recieved(contained)[source]
on_grenade(time_left)[source]
on_grenade_recieved(contained: pyspades.contained.GrenadePacket) → None[source]
on_grenade_thrown(grenade: pyspades.world.Grenade) → None[source]
on_hack_attempt(reason)[source]
on_handshake_recieved(contained: pyspades.contained.HandShakeReturn) → None[source]
on_hit(hit_amount, hit_player, kill_type, grenade)[source]
on_hit_recieved(contained)[source]
on_input_data_recieved(contained: pyspades.contained.InputData) → None[source]
on_join()[source]
on_kill(killer, kill_type, grenade)[source]
on_line_build(points)[source]
on_line_build_attempt(points)[source]
on_line_build_start()[source]

called when the player has pressed the mouse button to start line-building

on_login(name)[source]
on_new_player_recieved(contained: pyspades.contained.ExistingPlayer) → None[source]
on_orientation_update(x: float, y: float, z: float) → None[source]
on_orientation_update_recieved(contained: pyspades.contained.OrientationData) → None[source]
on_position_update() → None[source]
on_position_update_recieved(contained: pyspades.contained.PositionData) → None[source]
on_refill()[source]
on_reload_recieved(contained) → None[source]
on_reset()[source]
on_secondary_fire_set(secondary)[source]
on_shoot_set(fire: int) → None[source]
on_spawn(pos: Tuple[float, float, float]) → None[source]
on_spawn_location(pos: Tuple[float, float, float]) → None[source]
on_team_change_recieved(contained)[source]
on_team_changed(old_team: pyspades.team.Team) → None[source]
on_team_join(team)[source]
on_tool_change_recieved(contained: pyspades.contained.SetTool) → None[source]
on_tool_changed(tool: int) → None[source]
on_tool_set_attempt(tool: int) → None[source]
on_unvalidated_hit(hit_amount, hit_player, kill_type, grenade)[source]
on_version_info_recieved(contained: pyspades.contained.VersionResponse) → None[source]
on_walk_update(up: bool, down: bool, left: bool, right: bool) → None[source]
on_weapon_change_recieved(contained)[source]
on_weapon_input_recieved(contained: pyspades.contained.WeaponInput) → None[source]
on_weapon_set(value)[source]
player_id = None
rapid_hack_detect = False
refill(local: bool = False) → None[source]
reset() → None[source]
respawn() → None[source]
respawn_time = None
rubberband_distance = 10
saved_loaders = None
send_chat(value: str, global_message: bool = False, custom_type: int = 0) → None[source]
send_chat_error(message)[source]

Send a error message. This gets displayed as a red popup with sound for OpenSpades/Betterspades clients

send_chat_notice(message)[source]

Send a notice. This gets displayed as a popup for OpenSpades/Betterspades clients

send_chat_status(message)[source]

Send a status message. This gets displayed in the center of the screen for OpenSpades/Betterspades clients

send_chat_warning(message)[source]

Send a warning message. This gets displayed as a yellow popup with sound for OpenSpades/BetterSpades clients

send_data(data)[source]
send_map(data: Optional[pyspades.mapgenerator.ProgressiveMapGenerator] = None) → None[source]
set_hp(value: Union[int, float], hit_by: Optional[ServerConnection] = None, kill_type: int = 0, hit_indicator: Optional[Tuple[float, float, float]] = None, grenade: Optional[pyspades.world.Grenade] = None) → None[source]
set_location(location=None)[source]
set_location_safe(location, center=True)[source]
set_team(team)[source]
set_weapon(weapon: int, local: bool = False, no_kill: bool = False) → None[source]
spawn(pos: None = None) → None[source]
spawn_call = None
speedhack_detect = False
take_flag()[source]
team = None
timers = None
tool = None
weapon = None
weapon_object = None
world_object = None
pyspades.player.check_nan(*values) → bool[source]
pyspades.player.parse_command(value: str) → Tuple[str, Sequence[str]][source]

pyspades.protocol module

class pyspades.protocol.BaseConnection(protocol, peer)[source]

Bases: object

disconnect(data=0)[source]
disconnected = False
latency
loader_received(loader)[source]
on_connect()[source]
on_disconnect()[source]
send_contained(contained, sequence=False)[source]
timed_out()[source]
timeout_call = None
class pyspades.protocol.BaseProtocol(port=None, interface=b'*', update_interval=0.016666666666666666)[source]

Bases: object

check_client()[source]
connect(connection_class, host, port, version, channel_count=1, timeout=5.0)[source]
connection_class

alias of BaseConnection

data_received(peer, packet)[source]
is_client = False
max_connections = 33
on_connect(peer)[source]
on_disconnect(peer)[source]
remove_peer(peer)[source]
update()[source]

pyspades.server module

class pyspades.server.ServerProtocol(*arg, **kw)[source]

Bases: pyspades.protocol.BaseProtocol

blue_team

alias to team_1 for backwards-compatibility

broadcast_chat(message, global_message=None, sender=None, team=None)[source]
broadcast_chat_error(message, team=None)[source]

Send a warning message. This gets displayed as a red popup with sound for OpenSpades clients

broadcast_chat_notice(message, team=None)[source]

Send a warning message. This gets displayed as a popup for OpenSpades clients

broadcast_chat_status(message, team=None)[source]

Send a warning message. This gets displayed as a message in the status area at the top of the screen, where events such as intel pickups are also displayed.

broadcast_chat_warning(message, team=None)[source]

Send a warning message. This gets displayed as a yellow popup with sound for OpenSpades clients

broadcast_contained(contained, unsequenced=False, sender=None, team=None, save=False, rule=None)[source]

send a Contained Loader to all or a selection of connected players

Parameters:
  • contained – the Loader object to send
  • unsequenced – set the enet UNSEQUENCED flag on this packet
  • sender – if set to a connection object, do not send this packet to that player, as they are the sender.
  • team – if set to a team, only send the packet to that team
  • save – if the player has not downloaded the map yet, save this packet and send it when the map transfer has completed
  • rule – if set to a callable, this function is called with the player as parameter to determine if a given player should receive the packet
connection_class

alias of pyspades.player.ServerConnection

connections = None
fog_color = (128, 232, 255)
friendly_fire = False
friendly_fire_time = 2
game_mode = 0
get_cp_entities()[source]
get_fog_color()[source]
get_mode_mode()[source]
get_name(name)[source]

Sanitizes name and modifies it so that it doesn’t collide with other names connected to the server.

Returns the fixed name.

get_player_count()[source]
get_random_location(force_land=True, zone=(0, 0, 512, 512))[source]
got_master_connection(connection)[source]
green_team

alias to team_2 for backwards-compatibility

map = None
master = False
master_connection = None
master_disconnected(client=None)[source]
max_players = 32
max_score = 10
melee_damage = 100
name = 'pyspades server'
on_base_spawn(x, y, z, base, entity_id)[source]
on_cp_capture(cp)[source]
on_flag_spawn(x, y, z, flag, entity_id)[source]
on_game_end()[source]
on_map_change(map_)[source]
on_update_entity(entity)[source]
on_world_update()[source]
player_ids = None
refill_interval = 20
reset_game(player=None, territory=None)[source]

reset the score of the game

player is the player which should be awarded the necessary captures to end the game

reset_tc()[source]
respawn_time = 5
respawn_waves = False
send_chat(*args, **kwargs)[source]

Deprecated: see broadcast_chat

send_contained(*args, **kwargs)[source]

Deprecated: see broadcast_contained

server_prefix = '[*] '
set_fog_color(color)[source]
set_map(map_obj)[source]
set_master()[source]
spade_teamkills_on_grief = False
spectator_name = 'Spectator'
spectator_team

alias to team_spectator for backwards-compatibility

speedhack_detect = True
team1_color = (0, 0, 196)
team1_name = 'Blue'
team2_color = (0, 196, 0)
team2_name = 'Green'
team_class

alias of pyspades.team.Team

update()[source]
update_entities()[source]
update_master()[source]
update_network()[source]
version = 3
winning_player = None
world = None

pyspades.team module

class pyspades.team.Team(team_id: int, name: str, color: Tuple[int, int, int], spectator: bool, protocol: pyspades.protocol.BaseProtocol)[source]

Bases: object

base = None
count() → int[source]
flag = None
get_entities()[source]
get_entity_location(entity_id: int) → Tuple[int, int, int][source]
get_players() → None[source]
get_random_location(force_land: bool = False) → Tuple[int, int, int][source]
initialize() → None[source]
kills = None
name = None
other = None
protocol = None
score = None
set_base() → pyspades.entities.Base[source]
set_flag() → pyspades.entities.Flag[source]

pyspades.tools module

pyspades.tools.get_server_details(value)[source]
pyspades.tools.make_server_identifier(ip: ipaddress.IPv4Address, port: int = 32887) → str[source]

pyspades.types module

A few useful types used around the place.

IDPool is used to distribute the IDs given out by the Server

AttributeSet is used for testing if various settings are active

MultikeyDict is used to make player names accessible by both id and name

class pyspades.types.AttributeSet[source]

Bases: set

set with attribute access, i.e.

>>> foo = AttributeSet(("eggs", ))
>>> foo.eggs
True
>>> foo.spam
False

Also supports adding and removing elements

>>> foo.bar = True
>>> 'bar' in foo
True
>>> foo.bar = False
>>> 'bar' in foo
False

This works as a quick shorthand for membership testing.

class pyspades.types.IDPool(start=0)[source]

Bases: object

Manage pool of IDs

>>> p = IDPool(start=10)
>>> p.pop()
10
>>> p.pop()
11
>>> p.pop()
12
>>> p.put_back(11)
>>> p.pop()
11
pop()[source]
put_back(id)[source]
class pyspades.types.RateLimiter(event_count: int, seconds: float)[source]

Bases: object

sliding window rate limiter

Triggers if more than a certain number of events happen in a certain amount of time

above_limit() → bool[source]
get_events() → list[source]
record_event(timestamp: float) → None[source]

record an event at the given timestamp

pyspades.vxl module

class pyspades.vxl.Generator(VXLData data)

Bases: object

done

‘bool’

Type:done
get_data(self, int columns=2)
class pyspades.vxl.VXLData(fp=None)

Bases: object

build_point(self, int x, int y, int z, tuple color) → bool
check_node(self, int x, int y, int z, bool destroy=False) → int
copy(self)
count_land(self, int x1, y1, x2, y2)
destroy_point(self, int x, int y, int z)
generate(self)
get_color(self, int x, int y, int z)
get_generator(self)
get_height(self, int x, int y) → int
get_neighbors(self, int x, int y, int z) → list
get_overview(self, int z=-1, bool rgba=False)
get_point(self, int x, int y, int z)
get_random_point(self, int x1, int y1, int x2, int y2) → tuple
get_safe_coords(self, int x, int y, int z) → tuple

given (x, y, z) coords, return the closest set of coords on the map that is within the bounds of the map

get_solid(self, int x, int y, int z)
get_z(self, int x, int y, int start=0) → int

Returns the first z coordinate that is solid beginning from start and moving down. Useful for getting the coordinate for where something should be after being dropped.

has_neighbors(self, int x, int y, int z) → bool
is_surface(self, int x, int y, int z) → bool
is_valid_position(self, int x, int y, int z)

return if the value is a valid position within the bounds of the map

load_vxl(self, c_data=None)
remove_point(self, int x, int y, int z)
set_column_fast(self, int x, int y, int z_start, int z_end, int z_color_end, int color) → bool

Set a column’s solidity, but only color a limited amount from the top.

set_overview(self, data_str, int z)
set_point(self, int x, int y, int z, tuple color)
update_shadows(self)
pyspades.vxl.make_color(int r, int g, int b, int a=255) → int

pyspades.weapon module

class pyspades.weapon.BaseWeapon(reload_callback: Callable)[source]

Bases: object

ammo = None
damage = None
delay = 0.0
get_ammo(no_max: bool = False) → int[source]
get_damage(value, position1, position2) → int[source]
id = None
is_empty(tolerance=10) → bool[source]
next_shot = 0
on_reload() → None[source]
reload() → None[source]
reload_time = 0.0
reloading = False
reset() → None[source]
restock() → None[source]
set_shoot(value: bool) → None[source]
shoot = False
shoot_time = None
slow_reload = False
start = None
stock = None
class pyspades.weapon.Rifle(reload_callback: Callable)[source]

Bases: pyspades.weapon.BaseWeapon

ammo = 10
damage = {0: 49, 1: 100, 2: 33, 3: 33}
delay = 0.5
id = 0
name = 'Rifle'
reload_time = 2.5
slow_reload = False
stock = 50
class pyspades.weapon.SMG(reload_callback: Callable)[source]

Bases: pyspades.weapon.BaseWeapon

ammo = 30
damage = {0: 29, 1: 75, 2: 18, 3: 18}
delay = 0.11
id = 1
name = 'SMG'
reload_time = 2.5
slow_reload = False
stock = 120
class pyspades.weapon.Shotgun(reload_callback: Callable)[source]

Bases: pyspades.weapon.BaseWeapon

ammo = 6
damage = {0: 27, 1: 37, 2: 16, 3: 16}
delay = 1.0
id = 2
name = 'Shotgun'
reload_time = 0.5
slow_reload = True
stock = 48

pyspades.world module

class pyspades.world.Character

Bases: pyspades.world.Object

Represents the position, orientation and velocity of the player object in the world

airborne
can_see(self, float x, float y, float z) → int

return if the player can see a given coordinate. This only considers the map voxels, not any other objects

cast_ray(self, length=32.0)

cast a ray length number of blocks in the direction the player is facing, If a voxel is hit, return it’s coordinates, otherwise None

crouch
dead
down
fall_callback

object

Type:fall_callback
initialize(self, Vertex3 position, Vertex3 orientation, fall_callback=None)
jump
left
orientation

pyspades.common.Vertex3

Type:orientation
position

pyspades.common.Vertex3

Type:position
primary_fire
right
secondary_fire
set_animation(self, jump, crouch, sneak, sprint)

set all of the player’s movement statuses: jump, crouch, sneak and sprint

set_crouch(self, bool value)

set if the player is crouching

set_dead(self, value)

set the player’s alive status. Also resets mouse buttons, movement stats and keys

set_orientation(self, x, y, z)

set the current orientation of the Player

set_position(self, x, y, z, reset=False)

set the current position of the player. If reset=True is passed, reset velocity, keys, mouse buttons and movement status as well

set_walk(self, up, down, left, right)

set the current status of the movement buttons

set_weapon(self, is_primary)

set the primary weapon of the player

sneak
sprint
up
validate_hit(self, Character other, part, float aim_tolerance, float dist_tolerance)

check if a given hit is within a given tolerance of hitting another player. This is primarily used to prevent players from shooting at things they aren’t facing at

velocity

pyspades.common.Vertex3

Type:velocity
wade
class pyspades.world.Grenade

Bases: pyspades.world.Object

callback

object

Type:callback
fuse

‘float’

Type:fuse
get_damage(self, Vertex3 player_position) → double

Calculate the damage given to a player standing at player_position. Also performs a check to see if the player is behind cover.

get_next_collision(self, double dt)

calculate the position of the grenade ahead of time.

Returns:the ETA and the location of the next collision
Return type:eta, x, y, z
initialize(self, double fuse, Vertex3 position, Vertex3 orientation, Vertex3 velocity, callback=None)
position

pyspades.common.Vertex3

Type:position
team

object

Type:team
velocity

pyspades.common.Vertex3

Type:velocity
class pyspades.world.Object(world, *arg, **kw)

Bases: object

an object in present in the World

delete(self)

remove this object from the World

initialize(self, *arg, **kw)

hook called on Object creation

Arguments passed to __init__ will be passed here too.

name

object

Type:name
world

pyspades.world.World

Type:world
class pyspades.world.World

Bases: object

controls the map of the World and the Objects inside of it

create_object(self, klass, *arg, **kw)
delete_object(self, Object item)
map

pyspades.vxl.VXLData

Type:map
objects

list

Type:objects
time

‘float’

Type:time
update(self, double dt)
pyspades.world.cube_line(x1, y1, z1, x2, y2, z2)

create a cube line from one point to another with the same algorithm as the client uses

Contributing, Reporting Bugs and Requesting Features

In which ways I can contribute?

If you come across any issues, or have feature suggestions you would like to see in piqueserver, please inform us in our Issue Tracker.

Tip

Before reporting your problem or submitting a suggestion, search the Issue Tracker for similar issues.

Reporting bugs

When reporting bugs, please be as clear and detailed as possible. This way, we can work faster by saving time that would otherwise be spent asking questions and waiting between responses.

Common relevant information include:

  • Operating System (Windows, Mac OS X, GNU/Linux distro)
  • piqueserver version
  • Expected behaviour and what happened instead
  • Your console output containing the error

Submitting code changes in piqueserver

You can search inside these docs for information that might be relevant to your desired code changes. If you can’t find any, feel free to reach to us in chat ;).

Be sure to also check our Developer guidelines

Network ports

Piqueserver needs a few firewall ports open for various things.

The game server

The port the actual gameplay is on - this needs to be allowed at the bare minimum for players to connect to the game.

  • Default: 32887
  • Config variable: port
  • Protocol: udp

Status server

This is for the webpage which displays info about the server.

  • Default: 32886
  • Config variable: status_server.port
  • Protocol: tcp

Banpublish

For making the banlist public.

  • Default: 32885
  • Config variable: banpublish.port
  • Protocol: tcp

SSH

Some ssh server for remotely connecting to the server.

  • Default: 32887
  • Config variable: ssh.port
  • Protocol: tcp

List of somewhat relevant pyspades forks

What was happening with pyspades after development halted? Forks. This is the page to consolidate them

Note

iamgreaser’s BnS branch includes some extra powerthirst changes. The ‘prestine’ (kinda) BnS branch is only infogulch’s one

things worth checking: bots from infogulch

Technical details

External tools

  • aos-to-address by noway421 - Convert AoS URIs to their respective IP addresses

Note

The PySpades wiki has some legacy information that might be helpful.

Indices and tables