syntaxwebsite/app/pages/games/games.py

296 lines
17 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, jsonify, make_response, abort, flash
from app.util import auth, placeinfo
from app.models.asset import Asset
from app.models.placeservers import PlaceServer
from app.models.placeserver_players import PlaceServerPlayer
from app.models.gameservers import GameServer
from app.models.place import Place
from app.models.gamepass_link import GamepassLink
from app.models.userassets import UserAsset
from app.models.groups import Group, GroupRole, GroupRolePermission
from app.models.place_badge import PlaceBadge, UserBadge
from app.models.universe import Universe
from slugify import slugify
from app.pages.develop.develop import ShutdownServer
from app.enums.AssetType import AssetType
from app.models.user import User
from app.util.membership import GetUserMembership
from app.enums.MembershipType import MembershipType
from app.routes.rate import GetAssetLikesAndDislikes, GetUserVoteStatus, GetAssetVotePercentage, GetAssetFavoriteCount, GetUserFavoriteStatus
from app.routes.badgesapi import CalculateBadgeRarity, GetBadgeAwardedPastDay
from app.services import groups
from sqlalchemy import and_, func
from datetime import datetime, timedelta
import logging
GamePagesRoute = Blueprint("games", __name__, template_folder="pages")
def DoesUserownGamepass( gamepass_id, userid ):
GamepassObj : Asset = Asset.query.filter_by(id=gamepass_id, asset_type = AssetType.GamePass).first()
if GamepassObj is None:
return False
UserGamepass : UserAsset = UserAsset.query.filter_by(userid=userid, assetid=GamepassObj.id).first()
if UserGamepass is None:
return False
return True
def DoesUserOwnBadge( badge_id, userid ):
BadgeObj : PlaceBadge = PlaceBadge.query.filter_by(id=badge_id).first()
if BadgeObj is None:
return False
UserBadgeObj : UserBadge = UserBadge.query.filter_by(badge_id=badge_id, user_id=userid).first()
if UserBadgeObj is None:
return False
return True
def GetTotalBadgeAwardedCount( badge_id ) -> int:
return UserBadge.query.filter_by( badge_id = badge_id ).count()
@GamePagesRoute.route("/games/<int:placeid>/", methods=["GET"])
@GamePagesRoute.route("/games/<int:placeid>/<string:slug>", methods=["GET"])
@auth.authenticated_required
def game_page(placeid : int, slug=None):
AssetObject : Asset = Asset.query.filter_by(id=placeid).first()
if AssetObject is None:
return abort(404)
if AssetObject.asset_type != AssetType.Place:
# Let catalog handle the redirect
return redirect(f"/catalog/{str(placeid)}/")
SlugName = slugify(AssetObject.name, lowercase=False)
if SlugName is None or SlugName == "":
SlugName = "unnamed"
if slug is None:
return redirect(f"/games/{str(placeid)}/{SlugName}")
if slug != SlugName:
return redirect(f"/games/{str(placeid)}/{SlugName}")
if AssetObject.creator_type == 0:
CreatorObject : User = User.query.filter_by(id=AssetObject.creator_id).first()
else:
CreatorObject : Group = Group.query.filter_by(id=AssetObject.creator_id).first()
PlaceObj : Place = Place.query.filter_by(placeid=placeid).first()
if PlaceObj is None:
return abort(404)
UniverseObj : Universe = Universe.query.filter_by(id=PlaceObj.parent_universe_id).first()
ActualRootPlace : Asset = Asset.query.filter_by(id=UniverseObj.root_place_id).first()
try:
SplittedDescription = AssetObject.description.split("\n")
LikeCount, DislikeCount = GetAssetLikesAndDislikes(AssetObject.id)
AuthenticatedUser : User = auth.GetCurrentUser()
PlaceServerObjs : list[PlaceServer] = PlaceServer.query.filter_by(serverPlaceId=placeid).all()
ActiveServers = []
for PlaceServerObj in PlaceServerObjs:
PlayersInServer : list[PlaceServerPlayer] = PlaceServerPlayer.query.filter_by(serveruuid=PlaceServerObj.serveruuid).all()
GameServerObj : GameServer = GameServer.query.filter_by(serverId=PlaceServerObj.originServerId).first()
if GameServerObj is None:
continue
ActiveServers.append({
"id": PlaceServerObj.serveruuid,
"playercount": PlaceServerObj.playerCount,
"maxplayercount": PlaceServerObj.maxPlayerCount,
"players": PlayersInServer,
"host": GameServerObj.serverName,
"is_reserved_server": PlaceServerObj.reservedServerAccessCode is not None
})
Gamepasses : list[GamepassLink] = GamepassLink.query.filter_by(universe_id = PlaceObj.parent_universe_id).all()
UserDoesNotHaveBC : bool = UniverseObj.bc_required and GetUserMembership(AuthenticatedUser) == MembershipType.NonBuildersClub
CanShutdownServer = False if AuthenticatedUser.id != 1 else True
if AssetObject.creator_type == 0:
if AuthenticatedUser.id == AssetObject.creator_id:
CanShutdownServer = True
else:
GroupObj : Group = groups.GetGroupFromId( AssetObject.creator_id )
UserGroupRole : GroupRole = groups.GetUserRolesetInGroup( AuthenticatedUser, GroupObj )
if UserGroupRole is None:
CanShutdownServer = False
UserGroupRolePermission : GroupRolePermission = groups.GetRolesetPermission( UserGroupRole )
if UserGroupRolePermission.manage_group_games:
CanShutdownServer = True
GameBadges : list[PlaceBadge] = PlaceBadge.query.filter_by( universe_id = PlaceObj.parent_universe_id ).all()
return render_template("games/view.html", PlaceAssetObj=AssetObject,
CreatorObj=CreatorObject,
PlaceObj=PlaceObj,
SplittedDescription=SplittedDescription,
PlayerCount=placeinfo.GetPlayingCount(PlaceObj),
LikeCount=LikeCount,
DislikeCount=DislikeCount,
UserVoteStatus=GetUserVoteStatus(AssetObject.id, AuthenticatedUser.id),
UserFavoriteStatus=GetUserFavoriteStatus(AssetObject.id, AuthenticatedUser.id),
FavoriteCount=GetAssetFavoriteCount(AssetObject.id),
ActiveServers=ActiveServers,
ActiveServerCount=len(ActiveServers),
Gamepasses=Gamepasses,
DoesUserownGamepass=DoesUserownGamepass,
UserDoesNotHaveBC=UserDoesNotHaveBC,
IsTooYoung = (datetime.utcnow() - AuthenticatedUser.created).days < UniverseObj.minimum_account_age,
MinAccountAge = UniverseObj.minimum_account_age,
CanShutdownServer = CanShutdownServer,
GameBadges = GameBadges,
CalculateBadgeRarity = CalculateBadgeRarity,
DoesUserOwnBadge = DoesUserOwnBadge,
GetTotalBadgeAwardedCount = GetTotalBadgeAwardedCount,
GetBadgeAwardedPastDay = GetBadgeAwardedPastDay,
UniverseObj = UniverseObj,
UniverseRootPlace = ActualRootPlace
)
except Exception as e:
logging.error(f"Error during rendering game page [{placeid}]: {str(e)}")
return redirect(f"/games/{str(placeid)}/{SlugName}")
@GamePagesRoute.route("/games/<int:placeid>/shutdown-server/<jobid>", methods=["POST"])
@auth.authenticated_required
def game_shutdown_server(placeid : int, jobid : str):
AuthenticatedUser : User = auth.GetCurrentUser()
AssetObject : Asset = Asset.query.filter_by(id=placeid).first()
if AssetObject is None:
return abort(404)
if AssetObject.asset_type != AssetType.Place:
return abort(404)
TargetPlaceServer : PlaceServer = PlaceServer.query.filter_by(serveruuid=jobid).first()
if TargetPlaceServer is None:
return abort(404)
if (AssetObject.creator_id != AuthenticatedUser.id and AssetObject.creator_type != 1) and AuthenticatedUser.id != 1:
return abort(403)
if AssetObject.creator_type == 1:
GroupObj : Group = groups.GetGroupFromId( AssetObject.creator_id )
UserGroupRole : GroupRole = groups.GetUserRolesetInGroup( AuthenticatedUser, GroupObj )
if UserGroupRole is None:
return abort(403)
UserGroupRolePermission : GroupRolePermission = groups.GetRolesetPermission( UserGroupRole )
if not UserGroupRolePermission.manage_group_games:
return abort(403)
ShutdownServer(TargetPlaceServer.serveruuid)
flash("Requested server shutdown", "success")
return redirect(f"/games/{str(placeid)}/")
@GamePagesRoute.route("/games", methods=["GET"])
@auth.authenticated_required
def games_page():
TopPlacesInfo = []
PageNumber = max( request.args.get('page', default = 1, type = int ), 1 )
SearchQuery = request.args.get('q', default = None, type = str)
GameLookupResultsBackendResult = None
GameLookupResults = []
if SearchQuery:
SearchQuery = SearchQuery.strip().replace('%', '')
if len(SearchQuery) > 0 and len(SearchQuery) <= 32:
GameLookupResultsBackendResult = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).join( Asset, Asset.id == Place.placeid ).filter( Asset.name.ilike(f"%{SearchQuery}%") ).filter_by( moderation_status = 0 ).order_by( Universe.visit_count.desc() ).paginate(page=PageNumber, per_page=36, error_out=False)
else:
flash('Invalid search query.', 'danger')
if GameLookupResultsBackendResult:
for UniverseObj in GameLookupResultsBackendResult:
UniverseObj : Universe
AssetObj : Asset = Asset.query.filter_by(id=UniverseObj.root_place_id).first()
if AssetObj is None:
continue
PlaceObjData = {
"id": UniverseObj.root_place_id,
"name": AssetObj.name,
"playingcount": placeinfo.GetUniversePlayingCount(UniverseObj),
"slug": slugify(AssetObj.name, lowercase=False) if AssetObj.name is not None else "",
"likePercentage": GetAssetVotePercentage(AssetObj.id),
"placeyear": UniverseObj.place_year
}
GameLookupResults.append(PlaceObjData)
PopularGames = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).join( Asset, Asset.id == Place.placeid ).filter_by( moderation_status = 0 ).outerjoin( PlaceServer, PlaceServer.serverPlaceId == Place.placeid ).group_by( Universe.id )
PopularGames = PopularGames.order_by( func.coalesce( func.sum( PlaceServer.playerCount ), 0 ).desc() ).order_by( Universe.visit_count.desc() )
UniverseObjsList = PopularGames.limit(24).all()
for UniverseObj in UniverseObjsList:
UniverseObj : Universe
AssetObj : Asset = Asset.query.filter_by(id=UniverseObj.root_place_id).first()
if AssetObj is None:
continue
PlaceObjData = {
"id": UniverseObj.root_place_id,
"name": AssetObj.name,
"playingcount": placeinfo.GetUniversePlayingCount( UniverseObj ),
"slug": slugify(AssetObj.name, lowercase=False) if AssetObj.name is not None else "",
"likePercentage": GetAssetVotePercentage(UniverseObj.root_place_id),
"placeyear": UniverseObj.place_year
}
TopPlacesInfo.append(PlaceObjData)
RecentlyUpdatedUniverses : list[Universe] = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0) ).join( Asset, Asset.id == Universe.root_place_id ).filter_by( moderation_status = 0 ).order_by(Universe.updated_at.desc()).limit(24).all()
RecentlyUpdatedUniversesInfo = []
for UniverseObj in RecentlyUpdatedUniverses:
PlaceObj : Place = Place.query.filter_by(placeid=UniverseObj.root_place_id).first()
if PlaceObj is None:
continue
AssetObj : Asset = Asset.query.filter_by(id=PlaceObj.placeid).first()
if len(RecentlyUpdatedUniversesInfo) >= 24:
break
PlaceObjData = {
"id": AssetObj.id,
"name": AssetObj.name,
"playingcount": placeinfo.GetUniversePlayingCount( UniverseObj ),
"slug": slugify(AssetObj.name, lowercase=False) if AssetObj.name is not None else "",
"likePercentage": GetAssetVotePercentage(AssetObj.id),
"placeyear": UniverseObj.place_year
}
RecentlyUpdatedUniversesInfo.append(PlaceObjData)
FeaturedGames = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0, Universe.is_featured == True ) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).join( Asset, Asset.id == Place.placeid ).filter_by( moderation_status = 0 ).outerjoin( PlaceServer, PlaceServer.serverPlaceId == Place.placeid ).group_by( Universe.id )
FeaturedGames = FeaturedGames.order_by( func.coalesce( func.sum( PlaceServer.playerCount ), 0 ).desc() ).order_by( Universe.visit_count.desc() )
FeaturedPlaceObjs = FeaturedGames.limit(24).all()
FeaturedPlacesInfo = []
for UniverseObj in FeaturedPlaceObjs:
UniverseObj : Universe
AssetObj : Asset = Asset.query.filter_by(id=UniverseObj.root_place_id).first()
if AssetObj is None:
continue
PlaceObjData = {
"id": UniverseObj.root_place_id,
"name": AssetObj.name,
"playingcount": placeinfo.GetUniversePlayingCount( UniverseObj ),
"slug": slugify(AssetObj.name, lowercase=False) if AssetObj.name is not None else "",
"likePercentage": GetAssetVotePercentage(AssetObj.id),
"placeyear": UniverseObj.place_year
}
FeaturedPlacesInfo.append(PlaceObjData)
return render_template("games/index.html", PopularPlaces=TopPlacesInfo, RecentlyUpdatedPlaces=RecentlyUpdatedUniversesInfo, FeaturedPlacesInfo=FeaturedPlacesInfo, GameLookupResults = GameLookupResults, PageNumber=PageNumber, SearchQuery = SearchQuery, GameLookupResultsBackendResult = GameLookupResultsBackendResult)
from sqlalchemy import func
@GamePagesRoute.route("/games/<genre>/view", methods=["GET"])
@auth.authenticated_required
def games_genre_page(genre : str):
pageNumber = max( request.args.get("page", default=1, type=int), 1 )
if genre == "popular":
PopularGames = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).join(Asset, Asset.id == Place.placeid).outerjoin(PlaceServer, PlaceServer.serverPlaceId == Place.placeid).group_by(Universe.id)
PopularGames = PopularGames.filter( Asset.moderation_status == 0).order_by(func.coalesce(func.sum(PlaceServer.playerCount), 0).desc()).order_by( Universe.visit_count.desc() )
PopularGames = PopularGames.paginate(page=pageNumber, per_page=30, error_out=False)
ViewerFacingGenre = "Popular Games"
pageResults = PopularGames
elif genre == "updated":
UpdatedGames = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0) ).join( Asset, Asset.id == Universe.root_place_id ).filter( Asset.moderation_status == 0 ).order_by(Universe.updated_at.desc())
UpdatedGames = UpdatedGames.paginate(page=pageNumber, per_page=30, error_out=False)
ViewerFacingGenre = "Recently Updated"
pageResults = UpdatedGames
elif genre == "featured":
FeaturedGames = Universe.query.filter( and_( Universe.is_public == True, Universe.moderation_status == 0, Universe.is_featured == True ) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).join(Asset, Asset.id == Place.placeid).outerjoin(PlaceServer, PlaceServer.serverPlaceId == Place.placeid).group_by(Universe.id)
FeaturedGames = FeaturedGames.filter( Asset.moderation_status == 0).order_by(func.coalesce(func.sum(PlaceServer.playerCount), 0).desc()).order_by( Universe.visit_count.desc() )
FeaturedGames = FeaturedGames.paginate(page=pageNumber, per_page=30, error_out=False)
ViewerFacingGenre = "Featured Games"
pageResults = FeaturedGames
else:
return abort(404)
if pageResults is None:
return abort(404)
def getRootPlace( UniverseObj : Universe ) -> Place :
try:
return UniverseObj.root_place_obj
except:
UniverseObj.root_place_obj = Place.query.filter_by(placeid=UniverseObj.root_place_id).first()
return UniverseObj.root_place_obj
return render_template("games/genre.html", Games=pageResults, Genre=genre, getPlayerCount = placeinfo.GetUniversePlayingCount, getAssetVotePercentage = GetAssetVotePercentage, ViewerFacingGenre=ViewerFacingGenre, getRootPlace=getRootPlace)