예제 #1
0
class World(Base):
    """ A gaming world with a specific game.

    This groups game objects together.
    """

    # Textual name of this world
    name = DB.Column(DB.String(32), unique=True, nullable=False)
    # Whether this world is enabled or not
    enabled = DB.Column(DB.Boolean(), default=True, nullable=False)

    # Relationship to the game played in this world
    game_id = DB.Column(DB.Integer, DB.ForeignKey('game.id'))
    game = DB.relationship('Game',
                           backref=DB.backref('worlds', lazy='dynamic'))

    @api_method(authenticated=True)
    def player_join(self):
        """ Make the current player join this world.

        Called as an API method from a client.
        """

        # Check whether a user is logged in
        if g.user is None:
            # FIXME proper error
            return None

        # Check whether the user has a player in this world
        player = Player.query.filter_by(user=g.user, world=self).scalar()
        if player is None:
            # Create a new player in this world
            player = self.game.module.Player()
            player.name = g.user.username
            player.world = self
            player.user = g.user
            DB.session.add(player)
            DB.session.commit()

        # Update current_player
        g.user.current_player = player
        DB.session.add(g.user)
        DB.session.commit()

        # Redirect to new player object
        return redirect(url_for(player.__class__, resource_id=player.id))
        return redirect("/api/gameobject_player/%i" % player.id)
예제 #2
0
class Game(Base):
    """ A game known to the server.

    This model/table is automatically filled with all games
    known to the server from the _sync_games() initialisation
    function.

    It is used to keep a mapping to game modules within the
    data model.
    """

    # Base name of the game's Python package
    package = DB.Column(DB.String(128), nullable=False)
    # Filled from the respective game constants
    name = DB.Column(DB.String(32), nullable=False)
    version = DB.Column(DB.String(16), nullable=False)
    description = DB.Column(DB.String(1024))
    author = DB.Column(DB.String(32))
    license = DB.Column(DB.String(32))

    # The triple package,name,version needs to be unique
    __table_args__ = (DB.UniqueConstraint('package',
                                          'name',
                                          'version',
                                          name='_name_version_uc'), )

    @property
    def module(self):
        """ Attribute pointing to the real Python module loaded for the game. """

        # Determine the game module from the package name
        return get_game_by_name(self.package)

    @api_method(authenticated=True)
    def world_create(self, name=None):
        """ Create a world with this game.

        Called as an API method from a client.
        """

        # Create a new World object and store in database
        world = World()
        if name is None:
            world.name = self.name
        else:
            world.name = name
        world.game = self
        DB.session.add(world)
        DB.session.commit()

        # Redirect to new World object
        return redirect("/api/world/%i" % world.id)
예제 #3
0
class User(Base):
    """ A user account in the Veripeditus webserver.

    This is not a player object, it is only used for authentication and to
    link players to it.
    """

    # Login credentials
    # Password is automatically maintained in encrypted form through the PasswordType
    username = DB.Column(DB.String(32), unique=True, nullable=False)
    password = DB.Column(PasswordType(schemes=APP.config['PASSWORD_SCHEMES']),
                         nullable=False)

    # Relationship to the currently active player object for this account
    current_player_id = DB.Column(DB.Integer(),
                                  DB.ForeignKey("gameobject_player.id"))
    current_player = DB.relationship("veripeditus.framework.model.Player",
                                     foreign_keys=[current_player_id])

    # The role of this account in the server
    #    role = DB.Column(DB.Enum(Roles), default=Roles.player, nullable=False)
    role = DB.Column(DB.Unicode(32), default="PLAYER", nullable=False)

    @staticmethod
    def get_authenticated(username, password):
        """ Return a User object if username and password match,
        or None otherwise.
        """

        # Filter for username first
        user = User.query.filter_by(username=username).first()

        # Compare password if a user was found
        if user and user.password == password:
            # Return found user
            return user
        else:
            # Fallback to None
            return None
예제 #4
0
class GameObject(Base, metaclass=_GameObjectMeta):
    __tablename__ = "gameobject"

    _api_includes = ["world", "attributes"]

    id = DB.Column(DB.Integer(), primary_key=True)

    # Columns and relationships

    name = DB.Column(DB.String(32))
    image = DB.Column(DB.String(32), default="dummy", nullable=False)

    world_id = DB.Column(DB.Integer(), DB.ForeignKey("world.id"))
    world = DB.relationship("World", backref=DB.backref("gameobjects",
                                                        lazy="dynamic"),
                            foreign_keys=[world_id])

    longitude = DB.Column(DB.Float(), default=0.0, nullable=False)
    latitude = DB.Column(DB.Float(), default=0.0, nullable=False)

    osm_element_id = DB.Column(DB.Integer(), DB.ForeignKey("osm_elements.id"))
    osm_element = DB.relationship(OA.element, backref=DB.backref("osm_elements",
                                                                 lazy="dynamic"),
                                  foreign_keys=[osm_element_id])

    type = DB.Column(DB.Unicode(256))

    attributes = association_proxy("gameobjects_to_attributes", "value",
                                   creator=lambda k, v: GameObjectsToAttributes(
                                       attribute=Attribute(key=k, value=v)))

    distance_max = None

    available_images_pattern = ["*.svg", "*.png"]

    @property
    def gameobject_type(self):
        # Return type of gameobject
        return self.__tablename__

    def distance_to(self, obj):
        # Return distance to another gamobject
        return get_gameobject_distance(self, obj)

    @property
    def image_path(self):
        # Return path of image file
        return get_image_path(self.world.game.module, self.image)

    @hybrid_property
    def isonmap(self):
        return True

    @property
    def distance_to_current_player(self):
        # Return distance to current player
        if g.user is None or g.user.current_player is None:
            return None
        return self.distance_to(g.user.current_player)

    @api_method(authenticated=False)
    def image_raw(self, name=None):
        # Take path of current image if name is not given
        # If name is given take its path instead
        if name is None:
            image_path = self.image_path
        elif name in self.available_images():
            image_path = get_image_path(self.world.game.module, name)
        else:
            # FIXME correct error
            return None

        with open(image_path, "rb") as file:
            return file.read()

    @api_method(authenticated=True)
    def set_image(self, name):
        # Check if image is available
        if name in self.available_images():
            # Update image
            self.image = name
            self.commit()
        else:
            # FIXME correct error
            return None

        # Redirect to new image
        return redirect("/api/v2/gameobject/%d/image_raw" % self.id)

    @api_method(authenticated=False)
    def available_images(self):
        res = []

        if self.available_images_pattern is not None:
            # Make patterns a list
            if isinstance(self.available_images_pattern, list):
                patterns = self.available_images_pattern
            else:
                patterns = [self.available_images_pattern]

            # Get data path of this object's module
            data_path_game = get_data_path(self.world.game.module)
            # Get data path of the framework module
            data_path_framework = get_data_path()

            for data_path in (data_path_game, data_path_framework):
                for pattern in patterns:
                    # Get images in data path matching the pattern
                    res += glob(os.path.join(data_path, pattern))

        # Get basenames of every file without extension
        basenames = [os.path.extsep.join(os.path.basename(r).split(os.path.extsep)[:-1])
                     for r in res]

        # Return files in json format
        return json.dumps(basenames)

    @classmethod
    def spawn(cls, world=None):
        if world is None:
            # Iterate over all defined GameObject classes
            for go in cls.__subclasses__():
                # Iterate over all worlds using the game
                worlds = World.query.filter(World.game.has(package=go.__module__.split(".")[2])).all()
                for world in worlds:
                    if "spawn" in vars(go):
                        # Call spawn for each world
                        go.spawn(world)
                    else:
                        # Call parameterised default spawn code
                        go.spawn_default(world)

    @classmethod
    def spawn_default(cls, world):
        # Get current player
        current_player = None if g.user is None else g.user.current_player

        # Determine spawn location
        if "spawn_latlon" in vars(cls):
            latlon = cls.spawn_latlon

            if isinstance(latlon, Sequence):
                # We got one of:
                #  (lat, lon)
                #  ((lat, lon), (lat, lon),…)
                #  ((lat, lon), radius)
                if isinstance(latlon[0], Sequence) and isinstance(latlon[1], Sequence):
                    if len(latlon) == 2:
                        # We got a rect like ((lat, lon), (lat, lon))
                        # Randomise coordinates within that rect
                        latlon = (random.uniform(latlon[0][0], latlon[1][0]), random.uniform(latlon[0][1], latlon[1][1]))
                    else:
                        # We got a polygon, randomise coordinates within it
                        latlon = random_point_in_polygon(latlon)
                elif isinstance(latlon[0], Sequence) and isinstance(latlon[1], Real):
                    # We got a circle like ((lat, lon), radius)
                    # FIXME implement
                    raise RuntimeError("Not implemented.")
                elif isinstance(latlon[0], Real) and isinstance(latlon[1], Real):
                    # We got a single point like (lat, lon)
                    # Nothing to do, we can use that as is
                    pass
                else:
                    raise TypeError("Unknown value for spawn_latlon.")

            # Define a single spawn point with no linked OSM element
            spawn_points = {latlon: None}
        elif "spawn_osm" in vars(cls):
            # Skip if no current player or current player not in this world
            if current_player is None or current_player.world is not world:
                return

            # Define bounding box around current player
            # FIXME do something more intelligent here
            lat_min = current_player.latitude - 0.001
            lat_max = current_player.latitude + 0.001
            lon_min = current_player.longitude - 0.001
            lon_max = current_player.longitude + 0.001
            bbox_queries = [OA.node.latitude>lat_min, OA.node.latitude<lat_max,
                            OA.node.longitude>lon_min, OA.node.longitude<lon_max]

            # Build list of tag values using OSMAlchemy
            has_queries = [OA.node.tags.any(key=k, value=v) for k, v in cls.spawn_osm.items()]
            and_query = sa_and(*bbox_queries, *has_queries)

            # Do query
            # FIXME support more than plain nodes
            nodes = DB.session.query(OA.node).filter(and_query).all()

            # Extract latitude and longitude information and build spawn_points
            latlons = [(node.latitude, node.longitude) for node in nodes]
            spawn_points = dict(zip(latlons, nodes))
        else:
            # Do nothing if we cannot determine a location
            return

        for latlon, osm_element in spawn_points.items():
            # Determine existing number of objects on map
            existing = cls.query.filter_by(world=world, osm_element=osm_element, isonmap=True).count()
            if "spawn_min" in vars(cls) and "spawn_max" in vars(cls) and existing < cls.spawn_min:
                to_spawn = cls.spawn_max - existing
            elif existing == 0:
                to_spawn = 1
            else:
                to_spawn = 0

            # Spawn the determined number of objects
            for i in range(0, to_spawn):
                # Create a new object
                obj = cls()
                obj.world = world
                obj.latitude = latlon[0]
                obj.longitude = latlon[1]
                obj.osm_element = osm_element

                # Determine any defaults
                for k in vars(cls):
                    if k.startswith("default_"):
                        setattr(obj, k[8:], getattr(cls, k))

                # Derive missing defaults from class name
                if obj.image is None:
                    obj.image = cls.__name__.lower()
                if obj.name is None:
                    obj.name = cls.__name__.lower()

                # Add to session
                obj.commit()

    def commit(self):
        """ Commit this object to the database. """

        DB.session.add(self)
        DB.session.commit()