270 lines
11 KiB
Python
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 |