How it works
- A subclass of list to add Vector instances (player locations)
- creates/loads a JSON-formatted, Map-based <mapname>.json file
in/from your ../addons/source-python/data/plugins/<plugin>/spawnpoints folder
- when it saves the JSON file, it converts the Vector instances to lists of [X, Y, Z] coordinates to get the following style:As you can see, the JSON module can save them readable.
Code: Select all
[
[
-400.03125,
-162.86436462402344,
71.14938354492188
],
[
-415.63861083984375,
-438.2723693847656,
75.74543762207031
],
[
-162.5885467529297,
-988.04736328125,
130.56640625
]
]Syntax: Select all
import json
# use indent=4 to get the values as above:
json.dump(list_dict_or_whatever, file_object, indent=4)
- when it loads the JSON file, it will convert the [X, Y, Z] lists back to Vector instances
Files in ../addons/source-python/plugins/<plugin>:
Code: Select all
<plugin>.py
spawnpoints.py
Code: Select all
flashfunsp.py
spawnpoints.py
I'll use flashfunsp as "THE" plugin name for the tutorial's sake :)
The Code: spawnpoints.py
- 1. Imports
Syntax: Select all
# Python imports
# to load and save a Python list to a file, use the JSON library
from json import dump as json_dump
from json import load as json_load
# Source.Python imports
# Mathlib provides the Vector class
from mathlib import Vector
# ../addons/source-python/data/plugins
from paths import PLUGIN_DATA_PATH - 2. Global Variables
Syntax: Select all
# I'll use the path to my FlashFun.SP plugin:
# ../addons/source-python/data/plugins/flashfunsp/spawnpoints
spawnpoints_path = PLUGIN_DATA_PATH.joinpath("flashfunsp", "spawnpoints")
# some constants:
# maximum distance for spawning players
DISTANCE_SPAWN = 100.0
# maximum distance to add vectors
DISTANCE_ADD = 200.0
# maximum number of vectors per map
VECTORS_MAX = 35 - 3. The Class
Syntax: Select all
# ../addons/source-python/plugins/flashfunsp/spawnpoints.py
class _SpawnPoints(list):
""" Extends list for handling Vector instances as spawnpoints. """
def __init__(self):
"""
Instance initlaization
"""
# call list's __init__()
super(_SpawnPoints, self).__init__()
def append(self, vector):
"""
Checks the size of this list before appending the vector.
"""
# don't continue if the addition would exceed the limit
# or it already exists in this list
if len(self) >= VECTORS_MAX or vector in self:
return
# else, add the vector to the list
super(_SpawnPoints, self).append(vector)
def load(self, mapname):
"""
Loads the spawnpoints/<mapname>.json file if it exists and adds the vectors to this list.
"""
# clear the list if it's not empty
if self:
self.clear()
# get the full file path
file_path = spawnpoints_path / "{0}.json".format(mapname)
# don't continue if it doens't exist
if not file_path.isfile():
return
# open the file for reading
with file_path.open() as file_object:
# store the file's contents (lists of [X,Y,Z] coordinates)
contents = json_load(file_object)
# loop through the file's contents
for coords in contents:
# convert the coords to a Vector instance
# and append them to this list
self.append(Vector(*coords))
def save(self, mapname):
"""
Writes vectors from this list to the spawnpoints/<mapname>.json file.
"""
# don't continue if there is nothing to save
if not self:
return
# open the file for writing
with spawnpoints_path.joinpath("{0}.json".format(mapname)).open("w") as file_object:
# dump the vectors from this list as [X,Y,Z] lists to it
json_dump(list(map(lambda vector: [vector.x, vector.y, vector.z], self)), file_object, indent=4)
def check(self, vector):
"""
Returns whether the vector can be added to this list.
"""
# is the vector already in this list?
if vector in self:
# return False to stop the addition
return False
# loop through each vector in this list
for check in self:
# is the check too near?
if check.get_distance(vector) < DISTANCE_ADD:
# if yes, return False
return False
# else return True to add it
return True
# get an instance of the class
spawnpoints = _SpawnPoints()
Now we have defined a SpawnPoints class that can safely load from and write to JSON files in the ../addons/source-python/data/plugins/<plugin>/spawnpoints folder, and check if a vector can be added to the list. But one feature is still lacking: checking if there is a free SpawnPoint, and teleporting the player there.
You can add a function for this to the spawnpoints class, but I don't really feel that it should be there. It should be located either in the main file of the plugin or somewhere else, e.g. utils.py - in my opinion.
The function I came up with is a RECURSIVE function! If you haven't head anything about recursive programming, visit http://en.wikibooks.org/wiki/Non-Programmer's_Tutorial_for_Python_3/Recursion
Anyways, here's the function I came up with (in my case within flashfunsp.py):
Syntax: Select all
# we'll need to choose a RANDOM vector
# so let's import choice from random and give it a better name
from random import choice as random_choice
# import the spawnpoints instance in spawnpoints.py:
from flashfunsp.spawnpoints import spawnpoints
# we'll need the distance to check
from flashfunsp.spawnpoints import DISTANCE_PLAYERS
# give us the map name, please (global_vars.map_name)
from core import global_vars
# let us know when the map has changed
from listeners import LevelInit
# and when it's about to change
from listeners import LevelShutdown
# we want to loop through all alive players' locations
from filters.players import PlayerIter
# store a PlayerIter() generator for alive players that returns their locations
playeriter_origins = PlayerIter(is_filters="alive", return_types="location")
def get_random_spawnpoint(tried=0):
"""
Returns a random vector to spawn on.
"""
# is there anything to check?
if not spawnpoints:
# if not, return None
return None
# did we reach the maximum number of tries?
if tried == len(spawnpoints):
# if yes, return None
return None
# else, get a random vector
vector = random_choice(spawnpoints)
# loop trough alive player origins
for origin in playeriter_origins:
# is a player too near?
if vector.get_distance(origin) < DISTANCE_SPAWN:
# if yes, go recursive to find another vector...
return get_random_spawnpoint(tried + 1)
# retrun the random vector
return vector
Usage
Syntax: Select all
from events import Event
from players.helpers import index_from_userid
# somewhere in the script:
def get_random_spawnpoint(tried=0):
# ....
""" Add vectors as SpawnPoints """
@Event
def player_death(game_event):
# get a PlayerEntity instance of the victim
victim = PlayerEntity(index_of_userid(game_event.get_int("userid")))
# add the victim's location as a spawnpoint if it passed the check
if spawnpoints.check(victim.origin):
spawnpoints.append(victim.origin)
""" Teleport the player to a spawnpoint """
@Event
def player_spawn(game_event):
# get a PlayerEntity instance
player = PlayerEntity(index_of_userid(game_event.get_int("userid")))
# is the player alive and on a team?
# TeamID 2 = Terrorists, 3 = Counter-Terrorists, for CS:S
if player.get_property_int("pl.deadflag") or player.team < 2:
# if not, don't go any further
return
# get a random vector
vector = get_random_spawnpoint()
# is the vector valid?
if not vector is None:
# if yes, teleport the player there
player.origin = vector
""" Load the <mapname>.json file """
# on load
def load():
spawnpoints.load(global_vars.map_name)
# and on LevelInit
@LevelInit
def level_init(mapname):
# note: here we don't have to get the map name from global_vars since it was passed to the function
spawnpoints.load(mapname)
""" Save the <mapname>.json file """
# on unload
def unload():
spawnpoints.save(global_vars.map_name)
# and on LevelShutdown
@LevelShutdown
def level_shutdown():
spawnpoints.save(global_vars.map_name)
That's it! If you have any questions, feel free to post them :)