Added Habbo subscriptions and subscription purchasing

main
Tiger 2023-10-21 07:19:21 +02:00
parent b1c3bfd2b6
commit df75c4d4e6
16 changed files with 408 additions and 7 deletions

View File

@ -0,0 +1,38 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Outgoing.Catalog;
using Tiger.Communication.Messages.Types;
using Tiger.Game.Catalogue;
using Tiger.Networking.Game.Sessions;
namespace Tiger.Communication.Messages.Incoming.Catalog;
public class GetClubOffersMessageEvent : IMessageEvent
{
private readonly ICatalogueManager _catalogueManager;
private readonly IGameSessionManager _gameSessionManager;
public GetClubOffersMessageEvent(ICatalogueManager catalogueManager, IGameSessionManager gameSessionManager)
{
_catalogueManager = catalogueManager;
_gameSessionManager = gameSessionManager;
}
public IncomingHeaders Header => IncomingHeaders.GetClubOffers;
public async Task HandleAsync(GameSession gameSession, ClientMessage request)
{
if (gameSession.Habbo == null)
{
await _gameSessionManager.CloseAsync("Not logged in", gameSession);
return;
}
var currentSubscription = gameSession.Habbo.GetActiveSubscription();
await gameSession.SendComposerAsync(new HabboClubOffersMessageComposer(
_catalogueManager.ClubOffers.Values.Where(co =>
co.DiscountExtension == currentSubscription is
{
MonthsLeft: 0, DaysInMonthLeft: <= 5 and > 0
}).ToList(), currentSubscription));
}
}

View File

@ -0,0 +1,43 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Types;
using Tiger.Game.Catalogue;
using Tiger.Networking.Game.Sessions;
namespace Tiger.Communication.Messages.Incoming.Catalog;
public class PurchaseFromCatalogEvent : IMessageEvent
{
private readonly ICatalogueManager _catalogueManager;
private readonly IGameSessionManager _gameSessionManager;
public PurchaseFromCatalogEvent(ICatalogueManager catalogueManager, IGameSessionManager gameSessionManager)
{
_catalogueManager = catalogueManager;
_gameSessionManager = gameSessionManager;
}
public IncomingHeaders Header => IncomingHeaders.CatalogPurchase;
public async Task HandleAsync(GameSession gameSession, ClientMessage request)
{
if (gameSession.Habbo == null)
{
await _gameSessionManager.CloseAsync("Not logged in", gameSession);
return;
}
var pageId = request.ReadInt32();
var offerId = request.ReadInt32();
var extraData = request.ReadString();
var amount = request.ReadInt32();
if (amount < 0 || !_catalogueManager.Pages.TryGetValue(pageId, out var page))
{
return;
}
if (page.Layout.Equals("vip_buy"))
{
await _catalogueManager.PurchaseClubOffer(offerId, gameSession);
}
}
}

View File

@ -0,0 +1,34 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Outgoing.Inventory.Subscription;
using Tiger.Communication.Messages.Types;
using Tiger.Game.Achievements;
using Tiger.Networking.Game.Sessions;
namespace Tiger.Communication.Messages.Incoming.Inventory.Subscription;
public class UserSubscriptionEvent : IMessageEvent
{
private readonly IGameSessionManager _gameSessionManager;
private readonly IAchievementManager _achievementManager;
public UserSubscriptionEvent(IGameSessionManager gameSessionManager, IAchievementManager achievementManager)
{
_gameSessionManager = gameSessionManager;
_achievementManager = achievementManager;
}
public IncomingHeaders Header => IncomingHeaders.UserSubscription;
public async Task HandleAsync(GameSession gameSession, ClientMessage request)
{
if (gameSession.Habbo == null)
{
await _gameSessionManager.CloseAsync("Not logged in", gameSession);
return;
}
_achievementManager.UpdateAchievementAsync("ACH_HC", ((gameSession.Habbo.GetPastSubscriptionDays() / 31)), gameSession);
await gameSession.SendComposerAsync(new UserSubscriptionComposer(gameSession.Habbo.GetActiveSubscription(),
gameSession.Habbo.GetPastSubscriptionDays()));
}
}

View File

@ -0,0 +1,45 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Types;
using Tiger.Game.Catalogue;
using Tiger.Game.Habbos;
namespace Tiger.Communication.Messages.Outgoing.Catalog;
public class HabboClubOffersMessageComposer : IMessageComposer
{
private readonly ICollection<ClubOffer> _clubOffers;
private readonly HabboSubscription? _habboSubscription;
public HabboClubOffersMessageComposer(ICollection<ClubOffer> clubOffers, HabboSubscription? habboSubscription)
{
_clubOffers = clubOffers;
_habboSubscription = habboSubscription;
}
public OutgoingHeaders Header => OutgoingHeaders.ClubOffers;
public void Compose(ServerMessage message)
{
message.AppendInt32(_clubOffers.Count);
foreach (var clubOffer in _clubOffers)
{
var end = _habboSubscription is not null
? _habboSubscription.Expires.AddDays(clubOffer.Days)
: DateTime.Now.AddMonths(1);
message.AppendInt32(clubOffer.Id);
message.AppendString(clubOffer.Name);
message.AppendBoolean(false); // does absolutely NOTHING
message.AppendInt32(clubOffer.PriceCredits);
message.AppendInt32(clubOffer.PriceActivitypoints);
message.AppendInt32(clubOffer.ActivitypointsType);
message.AppendBoolean(true); // always vip
message.AppendInt32(0); // months
message.AppendInt32(clubOffer.Days); // extra days
message.AppendBoolean(true); // gift-able
message.AppendInt32(0); // days left after purchase
message.AppendInt32(end.Year);
message.AppendInt32(end.Month);
message.AppendInt32(end.Day);
}
}
}

View File

@ -0,0 +1,26 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Types;
namespace Tiger.Communication.Messages.Outgoing.Catalog;
public class NotEnoughBalanceMessageComposer : IMessageComposer
{
private readonly bool _notEnoughCredits;
private readonly bool _notEnoughActivityPoints;
private readonly int _activityPointsType;
public NotEnoughBalanceMessageComposer(bool notEnoughCredits, bool notEnoughActivityPoints, int activityPointsType)
{
_notEnoughCredits = notEnoughCredits;
_notEnoughActivityPoints = notEnoughActivityPoints;
_activityPointsType = activityPointsType;
}
public OutgoingHeaders Header => OutgoingHeaders.NotEnoughBalance;
public void Compose(ServerMessage message)
{
message.AppendBoolean(_notEnoughCredits);
message.AppendBoolean(_notEnoughActivityPoints);
message.AppendInt32(_activityPointsType);
}
}

View File

@ -0,0 +1,32 @@
using Tiger.Communication.Messages.Interfaces;
using Tiger.Communication.Messages.Types;
using Tiger.Game.Habbos;
namespace Tiger.Communication.Messages.Outgoing.Inventory.Subscription;
public class UserSubscriptionComposer : IMessageComposer
{
private readonly HabboSubscription? _habboSubscription;
private readonly int _pastSubscriptionDays;
public UserSubscriptionComposer(HabboSubscription? habboSubscription, int pastSubscriptionDays)
{
_habboSubscription = habboSubscription;
_pastSubscriptionDays = pastSubscriptionDays;
}
public OutgoingHeaders Header => OutgoingHeaders.UserSubscription;
public void Compose(ServerMessage message)
{
message.AppendString(_habboSubscription?.SubscriptionType ?? string.Empty);
message.AppendInt32(_habboSubscription?.DaysInMonthLeft ?? 0);
message.AppendInt32(_habboSubscription?.MonthsLeft ?? 0);
message.AppendInt32(0); // periods subscribed ahead but why is this different than above?
message.AppendInt32(0); // response type
message.AppendBoolean(_pastSubscriptionDays > 0);
message.AppendBoolean(true); // vip but always vip?
message.AppendInt32(0); // past club days but is always vip so below is used
message.AppendInt32(_pastSubscriptionDays);
message.AppendInt32((int)(_habboSubscription?.MinutesLeft ?? 0));
}
}

View File

@ -94,19 +94,23 @@ public class AchievementManager : IAchievementManager
}
}
await _badgesRepository.SaveAsync(badge);
_badgesRepository.SaveAsync(badge);
session.Habbo.AchievementScore += achievementLevel.Points;
session.Habbo.UpdateCurrency(achievementLevel.RewardType, achievementLevel.RewardAmount);
if (achievementLevel.RewardAmount > 0)
{
session.Habbo.UpdateCurrency(achievementLevel.RewardType, achievementLevel.RewardAmount);
session.SendComposerAsync(new ActivityPointNotificationMessageComposer(session.Habbo.Activitypoints[achievementLevel.RewardType].Amount, achievementLevel.RewardAmount, achievementLevel.RewardType));
}
// session.CurrentRoom?.SendMessage(new UserChangeMessageComposer(session.RoomUser.VirtualId, session.User));
session.SendComposerAsync(new AchievementsScoreComposer(session.Habbo.AchievementScore));
session.SendComposerAsync(new ActivityPointNotificationMessageComposer(session.Habbo.Activitypoints[achievementLevel.RewardType].Amount, achievementLevel.RewardAmount, achievementLevel.RewardType));
session.SendComposerAsync(new AchievementNotificationMessageComposer(habboAchievement, badge.Id));
}
await _habboRepository.SaveAsync(session.Habbo);
_habboRepository.SaveAsync(session.Habbo);
await session.SendComposerAsync(new AchievementComposer(achievement, habboAchievement));
}

View File

@ -1,4 +1,11 @@
using Microsoft.Extensions.Logging;
using Tiger.Communication.Messages.Outgoing.Catalog;
using Tiger.Communication.Messages.Outgoing.Inventory.Currency;
using Tiger.Communication.Messages.Outgoing.Inventory.Subscription;
using Tiger.Communication.Messages.Outgoing.Notifications;
using Tiger.Game.Achievements;
using Tiger.Game.Habbos;
using Tiger.Networking.Game.Sessions;
using Tiger.Storage;
namespace Tiger.Game.Catalogue;
@ -8,18 +15,27 @@ public class CatalogueManager : ICatalogueManager
private readonly IRepository<CataloguePage> _pagesRepository;
private readonly ILogger<ICatalogueManager> _logger;
private readonly IRepository<CatalogueFeaturedPage> _featuredPagesRepository;
private readonly IRepository<ClubOffer> _clubOfferRepository;
private readonly IRepository<HabboSubscription> _habboSubscriptionRepository;
private readonly IAchievementManager _achievementManager;
public IDictionary<int, CataloguePage> Pages { get; private set; }
public IDictionary<int, CatalogueFeaturedPage> FeaturedPages { get; private set; }
public IDictionary<int, ClubOffer> ClubOffers { get; private set; }
public CatalogueManager(IRepository<CataloguePage> pagesRepository, ILogger<ICatalogueManager> logger,
IRepository<CatalogueFeaturedPage> featuredPagesRepository)
IRepository<CatalogueFeaturedPage> featuredPagesRepository, IRepository<ClubOffer> clubOfferRepository,
IRepository<HabboSubscription> habboSubscriptionRepository, IAchievementManager achievementManager)
{
_pagesRepository = pagesRepository;
_logger = logger;
_featuredPagesRepository = featuredPagesRepository;
_clubOfferRepository = clubOfferRepository;
_habboSubscriptionRepository = habboSubscriptionRepository;
_achievementManager = achievementManager;
Pages = new Dictionary<int, CataloguePage>();
FeaturedPages = new Dictionary<int, CatalogueFeaturedPage>();
ClubOffers = new Dictionary<int, ClubOffer>();
}
public async Task LoadPagesAsync()
@ -31,9 +47,76 @@ public class CatalogueManager : ICatalogueManager
public async Task LoadFeaturedPagesAsync()
{
FeaturedPages =(await _featuredPagesRepository.FindByAsync())
FeaturedPages = (await _featuredPagesRepository.FindByAsync())
.ToDictionary(p => p.SlotId, p => p);
_logger.LogInformation("Loaded {Count} catalogue featured pages", FeaturedPages.Count);
}
public async Task LoadClubOffersAsync()
{
ClubOffers = (await _clubOfferRepository.FindByAsync())
.ToDictionary(co => co.Id);
_logger.LogInformation("Loaded {Count} club offers", ClubOffers.Count);
}
public async Task PurchaseClubOffer(int offerId, GameSession gameSession)
{
if (!ClubOffers.TryGetValue(offerId, out var clubOffer) || gameSession.Habbo == null)
{
return;
}
var tooShortOnCoins = clubOffer.PriceCredits > 0 && clubOffer.PriceCredits > gameSession.Habbo.Credits;
var tooShortOnActivityPoints = clubOffer.PriceActivitypoints > 0 && clubOffer.PriceActivitypoints >
gameSession.Habbo.GetActivityPoints(clubOffer.ActivitypointsType);
if (tooShortOnCoins || tooShortOnActivityPoints)
{
await gameSession.SendComposerAsync(new NotEnoughBalanceMessageComposer(tooShortOnCoins,
tooShortOnActivityPoints, clubOffer.ActivitypointsType));
return;
}
if (clubOffer.PriceCredits > 0)
{
gameSession.Habbo.Credits -= clubOffer.PriceCredits;
gameSession.SendComposerAsync(new UserCreditsComposer(gameSession.Habbo.Credits));
}
if (clubOffer.PriceActivitypoints > 0)
{
gameSession.Habbo.UpdateCurrency(clubOffer.ActivitypointsType, -clubOffer.PriceActivitypoints);
gameSession.SendComposerAsync(new ActivityPointNotificationMessageComposer(
gameSession.Habbo.GetActivityPoints(clubOffer.ActivitypointsType), -clubOffer.PriceActivitypoints,
clubOffer.ActivitypointsType));
}
var currentSubscription = gameSession.Habbo.GetActiveSubscription();
if (currentSubscription != null)
{
currentSubscription.Expires = currentSubscription.Expires.AddDays(clubOffer.Days);
currentSubscription.Habbo.Credits -= clubOffer.PriceCredits;
currentSubscription.Habbo.UpdateCurrency(clubOffer.ActivitypointsType, -clubOffer.PriceActivitypoints);
_habboSubscriptionRepository.SaveAsync(currentSubscription);
gameSession.SendComposerAsync(new UserSubscriptionComposer(currentSubscription,
gameSession.Habbo.GetPastSubscriptionDays()));
}
else
{
var subscription = new HabboSubscription
{
Expires = DateTime.Now.AddDays(clubOffer.Days),
SubscriptionType = "habbo_club",
Started = DateTime.Now,
Habbo = gameSession.Habbo
};
_habboSubscriptionRepository.SaveAsync(subscription);
gameSession.SendComposerAsync(new UserSubscriptionComposer(subscription,
gameSession.Habbo.GetPastSubscriptionDays()));
_achievementManager.UpdateAchievementAsync("ACH_HC", 1, gameSession);
}
}
}

View File

@ -0,0 +1,12 @@
namespace Tiger.Game.Catalogue;
public class ClubOffer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; } = null!;
public virtual int PriceCredits { get; set; }
public virtual int PriceActivitypoints { get; set; }
public virtual int ActivitypointsType { get; set; }
public virtual int Days { get; set; }
public virtual bool DiscountExtension { get; set; }
}

View File

@ -0,0 +1,18 @@
using FluentNHibernate.Mapping;
namespace Tiger.Game.Catalogue;
public class ClubOfferMap : ClassMap<ClubOffer>
{
public ClubOfferMap()
{
Table("catalogue_club_offers");
Id(co => co.Id).Column("id").GeneratedBy.Identity();
Map(co => co.Name).Column("name").Not.Nullable();
Map(co => co.PriceCredits).Column("price_credits").Not.Nullable();
Map(co => co.PriceActivitypoints).Column("price_activitypoints").Not.Nullable();
Map(co => co.ActivitypointsType).Column("activitypoints_type").Not.Nullable();
Map(co => co.Days).Column("days").Not.Nullable();
Map(co => co.DiscountExtension).Column("discount_extension").Not.Nullable();
}
}

View File

@ -1,9 +1,14 @@
using Tiger.Networking.Game.Sessions;
namespace Tiger.Game.Catalogue;
public interface ICatalogueManager
{
public IDictionary<int, CataloguePage> Pages { get; }
public IDictionary<int, CatalogueFeaturedPage> FeaturedPages { get; }
public IDictionary<int, ClubOffer> ClubOffers { get; }
Task LoadPagesAsync();
Task LoadFeaturedPagesAsync();
Task LoadClubOffersAsync();
Task PurchaseClubOffer(int offerId, GameSession gameSession);
}

View File

@ -21,7 +21,12 @@ public class Habbo
public virtual ICollection<Badge> Badges { get; set; } = new List<Badge>();
public virtual IDictionary<int, HabboAchievement> Achievements { get; set; } = new Dictionary<int, HabboAchievement>();
public virtual ICollection<HabboSubscription> Subscriptions { get; set; } = new List<HabboSubscription>();
public virtual int GetActivityPoints(int type)
{
return Activitypoints.TryGetValue(type, out var activityPoints) ? activityPoints.Amount : 0;
}
public virtual void UpdateCurrency(int type, int amount)
{
if (!Activitypoints.ContainsKey(type))
@ -37,4 +42,28 @@ public class Habbo
Activitypoints[type].Amount += amount;
}
public virtual HabboSubscription? GetActiveSubscription()
{
return Subscriptions.Where(s => s.Expires > DateTime.Now).MaxBy(s => s.Expires);
}
public virtual int GetPastSubscriptionDays()
{
int totalDays = 0;
foreach (var subscription in Subscriptions)
{
if(subscription.Expires < DateTime.Now) // If subscription has ended
{
totalDays += (subscription.Expires - subscription.Started).Days;
}
else if(subscription.Started < DateTime.Now) // If subscription is ongoing
{
totalDays += (DateTime.Now - subscription.Started).Days;
}
}
return totalDays;
}
}

View File

@ -30,11 +30,11 @@ public class HabboMap : ClassMap<Habbo>
.KeyColumn("habbo_id")
.Component(comp =>
{
// comp.References(x => x.Achievement, "achievement_id");
comp.Map(x => x.Level);
comp.Map(x => x.Progress);
})
.AsMap("achievement_id")
.Cascade.All();
HasMany(h => h.Subscriptions).Cascade.All().Inverse();
}
}

View File

@ -0,0 +1,14 @@
namespace Tiger.Game.Habbos;
public class HabboSubscription
{
public virtual int Id { get; set; }
public virtual Habbo Habbo { get; set; } = null!;
public virtual string SubscriptionType { get; set; }= null!;
public virtual DateTime Started { get; set; }
public virtual DateTime Expires { get; set; }
public virtual int DaysInMonthLeft => (int)(Expires - DateTime.Now).TotalDays % 31;
public virtual int MonthsLeft => (int)(Expires - DateTime.Now).TotalDays / 31;
public virtual double MinutesLeft => (Expires - DateTime.Now).TotalMinutes;
}

View File

@ -0,0 +1,17 @@
using FluentNHibernate.Mapping;
namespace Tiger.Game.Habbos;
public class HabboSubscriptionMap : ClassMap<HabboSubscription>
{
public HabboSubscriptionMap()
{
Table("habbo_subscriptions");
LazyLoad();
Id(hs => hs.Id).Column("id").GeneratedBy.Identity();
Map(hs => hs.SubscriptionType).Column("subscription_type").Not.Nullable();
Map(hs => hs.Started).Column("started").Not.Nullable();
Map(hs => hs.Expires).Column("expires").Not.Nullable();
References(x => x.Habbo).Column("habbo_id").Not.Nullable();
}
}

View File

@ -55,6 +55,7 @@ var provider = collection.BuildServiceProvider();
await provider.GetRequiredService<ISettingManager>().ReloadSettingsAsync();
await provider.GetRequiredService<ICatalogueManager>().LoadPagesAsync();
await provider.GetRequiredService<ICatalogueManager>().LoadFeaturedPagesAsync();
await provider.GetRequiredService<ICatalogueManager>().LoadClubOffersAsync();
provider.GetRequiredService<IFigureDataManager>();
await provider.GetRequiredService<ILandingViewManager>().LoadPromoArticlesAsync();
await provider.GetRequiredService<IAchievementManager>().LoadAchievementsAsync();