From 5ee2761e0cc62218ccc34d564131a2457f0b432a Mon Sep 17 00:00:00 2001 From: hero_124 Date: Thu, 8 Oct 2026 18:41:40 +0000 Subject: [PATCH] Economia: Robux e Tix (saldo, Tix diario, salario do Builders Club, compra no catalogo, pagina Money) --- README.md | 4 ++ RobloxServer.Web/Catalog.aspx.cs | 26 +++++-- RobloxServer.Web/Code/Data/Economy.cs | 89 ++++++++++++++++++++++++ RobloxServer.Web/Code/Data/Models.cs | 8 +++ RobloxServer.Web/Money.aspx | 7 ++ RobloxServer.Web/Money.aspx.cs | 80 +++++++++++++++++++++ RobloxServer.Web/RobloxServer.Web.csproj | 6 ++ RobloxServer.Web/Site.Master.cs | 5 +- 8 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 RobloxServer.Web/Code/Data/Economy.cs create mode 100644 RobloxServer.Web/Money.aspx create mode 100644 RobloxServer.Web/Money.aspx.cs diff --git a/README.md b/README.md index ef40b5a..d677550 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ Um **Raspberry Pi Zero 2W** faz o port forwarding, o UPnP e o DNS da rede. Veja Veja [docs/security-2015.md](docs/security-2015.md) para a lista completa das medidas de segurança (e das fraquezas mantidas de propósito). +### Robux e Tix + +Moedas **virtuais** (sem dinheiro de verdade). Todo dia de login rende T$ 10, e membros do Builders Club ganham também o salário diário de Robux (BC 15, TBC 35, OBC 60). No catálogo, o item custa R$ preço ou T$ preço x 10. O saldo aparece no topo; a página `Money.aspx` mostra o saldo e, para admin, permite dar ou tirar saldo de um usuário. + ### Modo bot (servidores 24/7) Todo jogo publicado (os que já existem e os novos) ganha um servidor mantido por um bot chamado **HOST**, sem ninguém diff --git a/RobloxServer.Web/Catalog.aspx.cs b/RobloxServer.Web/Catalog.aspx.cs index 7fa9802..05a054e 100644 --- a/RobloxServer.Web/Catalog.aspx.cs +++ b/RobloxServer.Web/Catalog.aspx.cs @@ -19,11 +19,22 @@ public class Catalog : SocialPage protected override string Handle(string verb, long id, User user) { - if (verb == "buy") + if (verb == "buy" || verb == "buytix") { CatalogItem item = CatalogService.Items.Find(i => i.Id == id); if (item == null) return "That item does not exist."; - if (!SocialStore.AddItem(user.Id, item.Id)) return "You already own this item."; + if (SocialStore.Owns(user.Id, item.Id)) return "You already own this item."; + int robux = verb == "buy" ? item.Price : 0; + int tix = verb == "buytix" ? item.Price * Economy.TixPerRobux : 0; + if ((robux > 0 || tix > 0) && !Economy.TryCharge(user.Id, robux, tix)) + { + return verb == "buy" ? "You do not have enough Robux." : "You do not have enough Tix."; + } + if (!SocialStore.AddItem(user.Id, item.Id)) + { + Economy.Grant(user.Id, robux, tix); + return "You already own this item."; + } CatalogService.Items.Update(i => i.Id == id, i => i.Sales++); NextUrl = Url("Inventory.aspx"); return null; @@ -149,7 +160,7 @@ string RenderItem(CatalogItem item, User user) sb.Append("
Category: " + E(item.Category) + "
"); sb.Append("
Created: " + item.Created.ToString("MMM d, yyyy", System.Globalization.CultureInfo.InvariantCulture) + "
"); sb.Append("
Sales: " + item.Sales + "
"); - sb.Append("
Price: " + (item.Price > 0 ? "R$ " + item.Price + " (free on this server)" : "Free") + "
"); + sb.Append("
Price: " + (item.Price > 0 ? "R$ " + item.Price + " or T$ " + (item.Price * Economy.TixPerRobux) : "Free") + "
"); sb.Append("

" + E(item.Description) + "

"); sb.Append("
" + Hidden(user)); if (user == null) @@ -162,7 +173,14 @@ string RenderItem(CatalogItem item, User user) } else { - sb.Append(Btn("Get this item", "buy:" + item.Id) + " "); + if (item.Price > 0) + { + sb.Append(Btn("Buy with R$ " + item.Price, "buy:" + item.Id) + " " + Btn("Buy with T$ " + (item.Price * Economy.TixPerRobux), "buytix:" + item.Id) + " "); + } + else + { + sb.Append(Btn("Get this item", "buy:" + item.Id) + " "); + } } sb.Append("Download .rbxm"); sb.Append("
"); diff --git a/RobloxServer.Web/Code/Data/Economy.cs b/RobloxServer.Web/Code/Data/Economy.cs new file mode 100644 index 0000000..289e513 --- /dev/null +++ b/RobloxServer.Web/Code/Data/Economy.cs @@ -0,0 +1,89 @@ +using System; + +namespace RobloxServer.Data +{ + /// + /// Robux (R$) e Tix (T$) virtuais: nenhum dinheiro de verdade. Tix diario para todo mundo e salario diario de Robux + /// para o Builders Club (BC 15, TBC 35, OBC 60). Catalogo: item custa R$ preco ou T$ preco x 10. + /// + public static class Economy + { + public const int DailyTix = 10; + public const int TixPerRobux = 10; + const int MaxBalance = 1000000000; + static readonly object Sync = new object(); + + public static int Stipend(string tier) + { + switch (tier) + { + case "BC": return 15; + case "TBC": return 35; + case "OBC": return 60; + default: return 0; + } + } + + /// Paga o Tix diario e o salario do Builders Club uma vez por dia (UTC). + public static void ClaimDaily(User user) + { + if (user == null) + { + return; + } + DateTime today = DateTime.UtcNow.Date; + lock (Sync) + { + User current = Db.Users.Find(u => u.Id == user.Id); + if (current == null || current.LastDaily >= today) + { + return; + } + int robux = Stipend(SocialStore.Tier(user.Id)); + Db.Users.Update(u => u.Id == user.Id, u => + { + u.LastDaily = today; + u.Tix = Clamp(u.Tix + DailyTix); + u.Robux = Clamp(u.Robux + robux); + }); + } + } + + static int Clamp(long value) + { + return (int)Math.Max(0, Math.Min(MaxBalance, value)); + } + + /// Cobra de uma vez so (sem saldo negativo). False se nao tiver saldo. + public static bool TryCharge(long userId, int robux, int tix) + { + lock (Sync) + { + User u = Db.Users.Find(x => x.Id == userId); + if (u == null || robux < 0 || tix < 0 || u.Robux < robux || u.Tix < tix) + { + return false; + } + Db.Users.Update(x => x.Id == userId, x => + { + x.Robux -= robux; + x.Tix -= tix; + }); + return true; + } + } + + /// Soma (ou tira, com valores negativos) saldo. Usado pelo admin e para estornos. + public static void Grant(long userId, int robux, int tix) + { + lock (Sync) + { + Db.Users.Update(x => x.Id == userId, x => + { + x.Robux = Clamp((long)x.Robux + robux); + x.Tix = Clamp((long)x.Tix + tix); + }); + } + } + } +} diff --git a/RobloxServer.Web/Code/Data/Models.cs b/RobloxServer.Web/Code/Data/Models.cs index 40348b7..4523b6d 100644 --- a/RobloxServer.Web/Code/Data/Models.cs +++ b/RobloxServer.Web/Code/Data/Models.cs @@ -24,6 +24,14 @@ public class User public string BodyColors { get; set; } + /// Virtual currency (Robux and Tix). No real money is involved. + public int Robux { get; set; } + + public int Tix { get; set; } + + /// Last UTC day the daily Tix and Builders Club stipend were paid. + public DateTime LastDaily { get; set; } + /// "Male", "Female" or null (2013 landing page signup). public string Gender { get; set; } diff --git a/RobloxServer.Web/Money.aspx b/RobloxServer.Web/Money.aspx new file mode 100644 index 0000000..574c120 --- /dev/null +++ b/RobloxServer.Web/Money.aspx @@ -0,0 +1,7 @@ +<%@ Page Title="My Stuff" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Money.aspx.cs" Inherits="RobloxServer.Pages.Money" %> + + + + + <%= Html %> + diff --git a/RobloxServer.Web/Money.aspx.cs b/RobloxServer.Web/Money.aspx.cs new file mode 100644 index 0000000..8a7cde2 --- /dev/null +++ b/RobloxServer.Web/Money.aspx.cs @@ -0,0 +1,80 @@ +using System.Text; +using RobloxServer.Data; +using RobloxServer.Web; + +namespace RobloxServer.Pages +{ + /// My Money: saldo de Robux/Tix, como ganhar, e (admin) dar ou tirar saldo. + public class Money : SocialPage + { + static int ToInt(string s) + { + int v; + int.TryParse((s ?? "").Trim(), out v); + return v; + } + + protected override string Handle(string verb, long id, User user) + { + if (verb != "grant") + { + return "Unknown action."; + } + if (!Db.IsAdmin(user)) + { + return "Only admins can change balances."; + } + User target = Db.FindUser((Request.Form["gname"] ?? "").Trim()); + if (target == null) + { + return "User not found."; + } + int robux = ToInt(Request.Form["grobux"]); + int tix = ToInt(Request.Form["gtix"]); + if (robux < -1000000 || robux > 1000000 || tix < -1000000 || tix > 1000000) + { + return "Use values between -1,000,000 and 1,000,000."; + } + Economy.Grant(target.Id, robux, tix); + Logging.Log(LogType.Success, "Admin " + user.Name + " granted R$ " + robux + " / T$ " + tix + " to " + target.Name); + NextUrl = Url("Money.aspx?ok=" + System.Uri.EscapeDataString(target.Name)); + return null; + } + + protected override string Render(User user) + { + Economy.ClaimDaily(user); + User me = Db.Users.Find(u => u.Id == user.Id) ?? user; + string tier = SocialStore.Tier(user.Id); + int stipend = Economy.Stipend(tier); + + var sb = new StringBuilder(); + sb.Append("

My Money

"); + string ok = Request.QueryString["ok"]; + if (!string.IsNullOrEmpty(ok)) + { + sb.Append("

Balance updated for " + E(ok) + ".

"); + } + sb.Append("

Robux: R$ " + me.Robux.ToString("N0") + "    Tix: T$ " + me.Tix.ToString("N0") + "

"); + sb.Append("

Every day you log in you get T$ " + Economy.DailyTix); + if (stipend > 0) + { + sb.Append(" and a stipend of R$ " + stipend + " (" + E(SocialStore.TierName(tier)) + ")"); + } + sb.Append(". Catalog items cost Robux, or " + Economy.TixPerRobux + " Tix for each R$. "); + sb.Append(stipend > 0 ? "" : "Builders Club members also get a daily Robux stipend. "); + sb.Append("Go to the Catalog.

"); + sb.Append("

Robux and Tix are virtual and cannot be bought with real money on this server.

"); + + if (Db.IsAdmin(user)) + { + sb.Append("

Admin: change a balance

" + Hidden(user) + + "User name " + + "R$ " + + "T$ " + + Btn("Apply", "grant:0") + "
"); + } + return sb.ToString(); + } + } +} diff --git a/RobloxServer.Web/RobloxServer.Web.csproj b/RobloxServer.Web/RobloxServer.Web.csproj index 8695413..f706315 100644 --- a/RobloxServer.Web/RobloxServer.Web.csproj +++ b/RobloxServer.Web/RobloxServer.Web.csproj @@ -101,6 +101,7 @@ + @@ -359,6 +360,10 @@ Inventory.aspx ASPXCodeBehind + + Money.aspx + ASPXCodeBehind + Groups.aspx ASPXCodeBehind @@ -376,6 +381,7 @@ ASPXCodeBehind + diff --git a/RobloxServer.Web/Site.Master.cs b/RobloxServer.Web/Site.Master.cs index a30ea28..0d10eed 100644 --- a/RobloxServer.Web/Site.Master.cs +++ b/RobloxServer.Web/Site.Master.cs @@ -18,7 +18,10 @@ protected void Page_Load(object sender, EventArgs e) AdminSubLink.Visible = isAdmin; if (user != null) { - UserNameLiteral.Text = Server.HtmlEncode(user.Name); + Economy.ClaimDaily(user); + User wallet = Db.Users.Find(u => u.Id == user.Id) ?? user; + UserNameLiteral.Text = Server.HtmlEncode(user.Name) + + " · R$ " + wallet.Robux.ToString("N0") + " · T$ " + wallet.Tix.ToString("N0") + ""; Over13Icon.Visible = !user.SuperSafeChat; }