SpawnPoints!

Post Python examples to help other users.
User avatar
BackRaw
Senior Member
Posts: 413
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany

SpawnPoints!

Postby BackRaw » Tue Jan 27, 2015 10:20 pm

After much trying out, I finally have put together a simple SpawnPoints class which can easily be implemented in your plugin.


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:

    Code: Select all

    [
        [
            -400.03125,
            -162.86436462402344,
            71.14938354492188
        ],
        [
            -415.63861083984375,
            -438.2723693847656,
            75.74543762207031
        ],
        [
            -162.5885467529297,
            -988.04736328125,
            130.56640625
        ]
    ]
    As you can see, the JSON module can save them readable.

    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
Since I have named my plugin flashfunsp, for me it would be:

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 :)
My Github repositories:

Source.Python: https://github.com/backraw/backraw.sp

Return to “Code examples / Cookbook”

Who is online

Users browsing this forum: No registered users and 1 guest