syntaxwebsite/app/services/economy.py

270 lines
11 KiB
Python

from app.extensions import redis_controller, db
from app.util import redislock
from app.models.usereconomy import UserEconomy
from app.models.user import User
from app.models.userassets import UserAsset
from app.models.groups import Group, GroupEconomy
from app.models.asset import Asset
from app.models.asset_rap import AssetRap
from app.services.groups import GetGroupFromId, GetUserFromId
import redis_lock
import math
class InvalidCurrencyTypeException(Exception):
pass
class EconomyLockAcquireException(Exception):
pass
class InsufficientFundsException(Exception):
pass
class AssetNotLimitedException(Exception):
pass
class AssetDoesNotExistException(Exception):
pass
def TaxCurrencyAmount( Amount : int ) -> int:
return math.floor( Amount * 0.7 )
def GetAssetFromId( assetid : int | Asset ) -> Asset | None:
"""
Returns the Asset object for the given assetid
"""
if isinstance(assetid, Asset):
return assetid
AssetObj : Asset = Asset.query.filter_by(id=assetid).first()
if AssetObj is None:
raise AssetDoesNotExistException("Asset does not exist")
return AssetObj
def GetUserEconomyObj( TargetUser : User ) -> UserEconomy | None:
"""
Returns the UserEconomy object for the given user
"""
return UserEconomy.query.filter_by( userid=TargetUser.id ).first()
def GetGroupEconomyObj( TargetGroup : Group ) -> GroupEconomy | None:
"""
Returns the GroupEconomy object for the given group
"""
return GroupEconomy.query.filter_by( group_id=TargetGroup.id ).first()
def GetUserBalance( TargetUser : User ) -> tuple[int, int]:
"""
Returns the User's Robux and Tickets balance
"""
EconomyObj : UserEconomy = GetUserEconomyObj( TargetUser )
return EconomyObj.robux, EconomyObj.tix
def GetGroupBalance( TargetGroup : Group ) -> tuple[int, int]:
"""
Returns the Group's Robux and Tickets balance
"""
EconomyObj : GroupEconomy = GetGroupEconomyObj( TargetGroup )
return EconomyObj.robux_balance, EconomyObj.tix_balance
def UnsafeIncrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
"""
Increments the Target Balance ( Not Recommended for normal use please instead use IncrementTargetBalance)
"""
if isinstance(Target, User):
TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
if CurrencyType == 0:
TargetEconomyObj.robux += Amount
elif CurrencyType == 1:
TargetEconomyObj.tix += Amount
else:
raise InvalidCurrencyTypeException("Invalid Currency Type")
db.session.commit()
elif isinstance(Target, Group):
TargetEconomyObj : GroupEconomy = GetGroupEconomyObj( Target )
if CurrencyType == 0:
TargetEconomyObj.robux_balance += Amount
elif CurrencyType == 1:
TargetEconomyObj.tix_balance += Amount
else:
raise InvalidCurrencyTypeException("Invalid Currency Type")
db.session.commit()
else:
raise TypeError("Invalid Target Type")
def UnsafeDecrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
"""
Decrements the Target Balance ( Not Recommended for normal use please instead use DecrementTargetBalance)
"""
if isinstance(Target, User):
TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
if CurrencyType == 0:
TargetEconomyObj.robux -= Amount
elif CurrencyType == 1:
TargetEconomyObj.tix -= Amount
else:
raise InvalidCurrencyTypeException("Invalid Currency Type")
db.session.commit()
elif isinstance(Target, Group):
TargetEconomyObj : GroupEconomy = GetGroupEconomyObj( Target )
if CurrencyType == 0:
TargetEconomyObj.robux_balance -= Amount
elif CurrencyType == 1:
TargetEconomyObj.tix_balance -= Amount
else:
raise InvalidCurrencyTypeException("Invalid Currency Type")
db.session.commit()
else:
raise TypeError("Invalid Target Type")
def IncrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
"""
Increments the Target Balance
"""
if Amount < 0:
raise ValueError("Amount must be positive")
if isinstance(Target, User):
with redis_lock.Lock( redis_client = redis_controller, name = f"economy:{Target.id}", expire = 1, auto_renewal = True ):
UnsafeIncrementTargetBalance( Target, Amount, CurrencyType )
return
elif isinstance(Target, Group):
with redis_lock.Lock( redis_client = redis_controller, name = f"economy_group:{Target.id}", expire = 1, auto_renewal = True ):
UnsafeIncrementTargetBalance( Target, Amount, CurrencyType )
return
else:
raise TypeError("Invalid Target Type")
def DecrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
"""
Decrements the Target Balance
"""
if Amount < 0:
raise ValueError("Amount must be positive")
if isinstance(Target, User):
with redis_lock.Lock( redis_client = redis_controller, name = f"economy:{Target.id}", expire = 1, auto_renewal = True ):
TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
if CurrencyType == 0:
if TargetEconomyObj.robux < Amount:
raise InsufficientFundsException("Insufficient Funds")
elif CurrencyType == 1:
if TargetEconomyObj.tix < Amount:
raise InsufficientFundsException("Insufficient Funds")
UnsafeDecrementTargetBalance( Target, Amount, CurrencyType )
return
elif isinstance(Target, Group):
with redis_lock.Lock( redis_client = redis_controller, name = f"economy_group:{Target.id}", expire = 1, auto_renewal = True ):
TargetEconomyObj : GroupEconomy = GroupEconomy.query.filter_by( group_id=Target.id ).first()
if CurrencyType == 0:
if TargetEconomyObj.robux_balance < Amount:
raise InsufficientFundsException("Insufficient Funds")
elif CurrencyType == 1:
if TargetEconomyObj.tix_balance < Amount:
raise InsufficientFundsException("Insufficient Funds")
UnsafeDecrementTargetBalance( Target, Amount, CurrencyType )
return
else:
raise TypeError("Invalid Target Type")
def TransferFunds( Source : User | Group, Target : User | Group, Amount : int, CurrencyType : int, ApplyTax : bool = False): # CurrencyType is 0 for Robux, 1 for Tickets
"""
Transfers funds from the source to the target
"""
if Amount < 0:
raise ValueError("Amount must be positive")
if Source == Target:
raise ValueError("Source and Target must be different")
if CurrencyType not in [0,1]:
raise InvalidCurrencyTypeException("Invalid Currency Type")
if isinstance(Source, User):
SourceEconomyObj : UserEconomy = GetUserEconomyObj( Source )
if CurrencyType == 0:
if SourceEconomyObj.robux < Amount:
raise InsufficientFundsException("Insufficient Funds")
elif CurrencyType == 1:
if SourceEconomyObj.tix < Amount:
raise InsufficientFundsException("Insufficient Funds")
elif isinstance(Source, Group):
SourceEconomyObj : GroupEconomy = GroupEconomy.query.filter_by( group_id=Source.id ).first()
if CurrencyType == 0:
if SourceEconomyObj.robux_balance < Amount:
raise InsufficientFundsException("Insufficient Funds")
elif CurrencyType == 1:
if SourceEconomyObj.tix_balance < Amount:
raise InsufficientFundsException("Insufficient Funds")
else:
raise TypeError("Invalid Source Type")
TakenAmount : int = Amount
GivenAmount : int = Amount
if ApplyTax:
GivenAmount = TaxCurrencyAmount( Amount )
try:
DecrementTargetBalance( Source, TakenAmount, CurrencyType )
except InsufficientFundsException:
raise InsufficientFundsException("Insufficient Funds")
IncrementTargetBalance( Target, GivenAmount, CurrencyType )
return
def AdjustAssetRap(AssetObj : Asset | int, robux : int):
""" Adjusts the RAP of an asset
https://roblox.fandom.com/wiki/Recent_Average_Price
This will only work with assets that are limited
"""
AssetObj : Asset = GetAssetFromId(AssetObj)
AssetRapObject : AssetRap = AssetRap.query.filter_by(assetid=AssetObj.id).first()
if AssetRapObject is None:
if not AssetObj.is_limited:
raise AssetNotLimitedException("Asset is not limited")
AssetRapObject = AssetRap(assetid=AssetObj.id, rap=robux)
db.session.add(AssetRapObject)
db.session.commit()
return True
if AssetRapObject.rap <= 0:
AssetRapObject.rap = robux
CurrentRAP = AssetRapObject.rap
AssetRapObject.rap = math.floor(CurrentRAP - ( CurrentRAP - robux ) / 10)
db.session.commit()
return True
def GetAssetRap(AssetObj : Asset | int ) -> int:
"""
Returns the RAP of an asset
"""
AssetObj : Asset = GetAssetFromId(AssetObj)
if not AssetObj.is_limited:
raise AssetNotLimitedException("Asset is not limited")
AssetRapObject : AssetRap = AssetRap.query.filter_by(assetid=AssetObj.id).first()
if AssetRapObject is None:
AssetRapObject = AssetRap(assetid=AssetObj.id, rap=0)
db.session.add(AssetRapObject)
db.session.commit()
return AssetRapObject.rap
def GetCreatorOfAsset( AssetObj : Asset | int ) -> User | Group | None:
"""
Returns the creator of an asset
"""
AssetObj : Asset = GetAssetFromId(AssetObj)
if AssetObj.creator_type == 1:
return GetGroupFromId(AssetObj.creator_id)
elif AssetObj.creator_type == 0:
return GetUserFromId(AssetObj.creator_id)
else:
return None
def CalculateUserRAP( UserObj : User | int, skipCache : bool = False ) -> int:
"""
Calculates the RAP of a user
"""
if redis_controller.exists(f"rap_calculation:{UserObj.id}") and not skipCache:
return int(redis_controller.get(f"rap_calculation:{UserObj.id}"))
UserObj : User = GetUserFromId(UserObj)
UserRAP : int = 0
UserLimitedAssets : list[UserAsset] = UserAsset.query.filter_by(userid=UserObj.id).outerjoin( Asset, Asset.id == UserAsset.assetid ).filter( Asset.is_limited == True ).all()
for UserLimitedAsset in UserLimitedAssets:
UserRAP += GetAssetRap(UserLimitedAsset.assetid)
redis_controller.set(f"rap_calculation:{UserObj.id}", UserRAP, ex = 60)
return UserRAP