Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 22 additions & 4 deletions RobloxServer.Web/Catalog.aspx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -149,7 +160,7 @@ string RenderItem(CatalogItem item, User user)
sb.Append("<div><b>Category:</b> <a href=\"" + Url("Catalog.aspx?c=" + Uri.EscapeDataString(item.Category ?? "")) + "\">" + E(item.Category) + "</a></div>");
sb.Append("<div><b>Created:</b> " + item.Created.ToString("MMM d, yyyy", System.Globalization.CultureInfo.InvariantCulture) + "</div>");
sb.Append("<div><b>Sales:</b> " + item.Sales + "</div>");
sb.Append("<div><b>Price:</b> " + (item.Price > 0 ? "R$ " + item.Price + " (free on this server)" : "Free") + "</div>");
sb.Append("<div><b>Price:</b> " + (item.Price > 0 ? "R$ " + item.Price + " or T$ " + (item.Price * Economy.TixPerRobux) : "Free") + "</div>");
sb.Append("<p>" + E(item.Description) + "</p>");
sb.Append("<div>" + Hidden(user));
if (user == null)
Expand All @@ -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("<a class=\"Button\" href=\"" + Url("Asset/Rbxm.ashx?id=" + item.Id) + "\">Download .rbxm</a>");
sb.Append("</div></div></div>");
Expand Down
89 changes: 89 additions & 0 deletions RobloxServer.Web/Code/Data/Economy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;

namespace RobloxServer.Data
{
/// <summary>
/// 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.
/// </summary>
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;
}
}

/// <summary>Paga o Tix diario e o salario do Builders Club uma vez por dia (UTC).</summary>
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));
}

/// <summary>Cobra de uma vez so (sem saldo negativo). False se nao tiver saldo.</summary>
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;
}
}

/// <summary>Soma (ou tira, com valores negativos) saldo. Usado pelo admin e para estornos.</summary>
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);
});
}
}
}
}
8 changes: 8 additions & 0 deletions RobloxServer.Web/Code/Data/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class User

public string BodyColors { get; set; }

/// <summary>Virtual currency (Robux and Tix). No real money is involved.</summary>
public int Robux { get; set; }

public int Tix { get; set; }

/// <summary>Last UTC day the daily Tix and Builders Club stipend were paid.</summary>
public DateTime LastDaily { get; set; }

/// <summary>"Male", "Female" or null (2013 landing page signup).</summary>
public string Gender { get; set; }

Expand Down
7 changes: 7 additions & 0 deletions RobloxServer.Web/Money.aspx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%@ Page Title="My Stuff" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Money.aspx.cs" Inherits="RobloxServer.Pages.Money" %>
<asp:Content ID="Head" ContentPlaceHolderID="HeadContent" runat="server">
<link href="~/Content/Pages/Social2013.css" rel="stylesheet" type="text/css" runat="server" />
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<%= Html %>
</asp:Content>
80 changes: 80 additions & 0 deletions RobloxServer.Web/Money.aspx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Text;
using RobloxServer.Data;
using RobloxServer.Web;

namespace RobloxServer.Pages
{
/// <summary>My Money: saldo de Robux/Tix, como ganhar, e (admin) dar ou tirar saldo.</summary>
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("<h2>My Money</h2>");
string ok = Request.QueryString["ok"];
if (!string.IsNullOrEmpty(ok))
{
sb.Append("<p style=\"color:#060\">Balance updated for " + E(ok) + ".</p>");
}
sb.Append("<p><b>Robux:</b> R$ " + me.Robux.ToString("N0") + " &nbsp;&nbsp; <b>Tix:</b> T$ " + me.Tix.ToString("N0") + "</p>");
sb.Append("<p>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("<a href=\"" + Url("Catalog.aspx") + "\">Go to the Catalog</a>.</p>");
sb.Append("<p style=\"color:#666\">Robux and Tix are virtual and cannot be bought with real money on this server.</p>");

if (Db.IsAdmin(user))
{
sb.Append("<h2>Admin: change a balance</h2><div>" + Hidden(user)
+ "User name <input type=\"text\" name=\"gname\" maxlength=\"30\" /> "
+ "R$ <input type=\"text\" name=\"grobux\" value=\"0\" size=\"8\" /> "
+ "T$ <input type=\"text\" name=\"gtix\" value=\"0\" size=\"8\" /> "
+ Btn("Apply", "grant:0") + "</div>");
}
return sb.ToString();
}
}
}
6 changes: 6 additions & 0 deletions RobloxServer.Web/RobloxServer.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
<Content Include="Messages.aspx" />
<Content Include="Friends.aspx" />
<Content Include="Inventory.aspx" />
<Content Include="Money.aspx" />
<Content Include="Groups.aspx" />
<Content Include="Trade.aspx" />
<Content Include="BuildersClub.aspx" />
Expand Down Expand Up @@ -359,6 +360,10 @@
<DependentUpon>Inventory.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Money.aspx.cs">
<DependentUpon>Money.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Groups.aspx.cs">
<DependentUpon>Groups.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
Expand All @@ -376,6 +381,7 @@
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Code\Data\Catalog.cs" />
<Compile Include="Code\Data\Economy.cs" />
<Compile Include="Code\Data\Social.cs" />
<Compile Include="Code\Web\SocialPage.cs" />
<Compile Include="Code\Data\XmlTable.cs" />
Expand Down
5 changes: 4 additions & 1 deletion RobloxServer.Web/Site.Master.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
+ " <span style=\"font-size:11px\">&middot; R$ " + wallet.Robux.ToString("N0") + " &middot; T$ " + wallet.Tix.ToString("N0") + "</span>";
Over13Icon.Visible = !user.SuperSafeChat;
}

Expand Down
Loading