Implements item pricing, and piracy. (#8548)
* Start implementing item pricing. * Flesh out prices a bit, add the appraisal tool. * Add prices to more things. * YARRRRRRR * gives pirates an appraisal tool in their pocket. * Makes the various traitor objectives valuable. Also nerfs the price of a living person, so it's easier to bargain for them. * Address reviews. * Address reviews.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Cargo;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Shared.Cargo.Components;
|
||||
|
||||
namespace Content.Server.Cargo.Components
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.MachineLinking;
|
||||
|
||||
26
Content.Server/Cargo/Components/MobPriceComponent.cs
Normal file
26
Content.Server/Cargo/Components/MobPriceComponent.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for calculating the price of mobs.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class MobPriceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much of a penalty per part there should be. This is a multiplier for a multiplier, the penalty for each body part is calculated from the total number of slots, and then multiplied by this.
|
||||
/// </summary>
|
||||
[DataField("missingBodyPartPenalty")]
|
||||
public double MissingBodyPartPenalty = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The base price this mob should fetch.
|
||||
/// </summary>
|
||||
[DataField("price", required: true)]
|
||||
public double Price;
|
||||
|
||||
/// <summary>
|
||||
/// The percentage of the actual price that should be granted should the appraised mob be dead.
|
||||
/// </summary>
|
||||
[DataField("deathPenalty")]
|
||||
public double DeathPenalty = 0.2f;
|
||||
}
|
||||
10
Content.Server/Cargo/Components/PriceGunComponent.cs
Normal file
10
Content.Server/Cargo/Components/PriceGunComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the price gun, which calculates the price of any object it appraises.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class PriceGunComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
14
Content.Server/Cargo/Components/StackPriceComponent.cs
Normal file
14
Content.Server/Cargo/Components/StackPriceComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for pricing stacks of items.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StackPriceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The price of the object this component is on, per unit.
|
||||
/// </summary>
|
||||
[DataField("price", required: true)]
|
||||
public double Price;
|
||||
}
|
||||
14
Content.Server/Cargo/Components/StaticPriceComponent.cs
Normal file
14
Content.Server/Cargo/Components/StaticPriceComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for setting a static, unchanging price for an object.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StaticPriceComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The price of the object this component is on.
|
||||
/// </summary>
|
||||
[DataField("price", required: true)]
|
||||
public double Price;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.GameTicking;
|
||||
|
||||
namespace Content.Server.Cargo
|
||||
namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
@@ -6,7 +6,7 @@ using Content.Shared.Cargo;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Cargo;
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Cargo;
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
public sealed partial class CargoSystem : SharedCargoSystem
|
||||
{
|
||||
37
Content.Server/Cargo/Systems/PriceGunSystem.cs
Normal file
37
Content.Server/Cargo/Systems/PriceGunSystem.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class PriceGunSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PriceGunComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, PriceGunComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
if (TryComp(args.Used, out UseDelayComponent? useDelay) && useDelay.ActiveDelay)
|
||||
return;
|
||||
|
||||
var price = _pricingSystem.GetPrice(args.Target.Value);
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", args.Target.Value), ("price", $"{price:F2}")), args.User, Filter.Entities(args.User));
|
||||
_useDelay.BeginDelay(uid, useDelay);
|
||||
}
|
||||
}
|
||||
190
Content.Server/Cargo/Systems/PricingSystem.cs
Normal file
190
Content.Server/Cargo/Systems/PricingSystem.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Materials;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles calculating the price of items, and implements two basic methods of pricing materials.
|
||||
/// </summary>
|
||||
public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<StaticPriceComponent, PriceCalculationEvent>(CalculateStaticPrice);
|
||||
SubscribeLocalEvent<StackPriceComponent, PriceCalculationEvent>(CalculateStackPrice);
|
||||
SubscribeLocalEvent<MobPriceComponent, PriceCalculationEvent>(CalculateMobPrice);
|
||||
|
||||
_consoleHost.RegisterCommand("appraisegrid",
|
||||
"Calculates the total value of the given grids.",
|
||||
"appraisegrid <grid Ids>", AppraiseGridCommand);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
private void AppraiseGridCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Not enough arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var gid in args)
|
||||
{
|
||||
if (!int.TryParse(gid, out var i) || i <= 0)
|
||||
{
|
||||
shell.WriteError($"Invalid grid ID \"{gid}\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
var gridId = new GridId(i);
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out var mapGrid))
|
||||
{
|
||||
shell.WriteError($"Grid \"{i}\" doesn't exist.");
|
||||
continue;
|
||||
}
|
||||
|
||||
List<(double, EntityUid)> mostValuable = new();
|
||||
|
||||
var value = AppraiseGrid(mapGrid.GridEntityId, null, (uid, price) =>
|
||||
{
|
||||
mostValuable.Add((price, uid));
|
||||
mostValuable.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
|
||||
if (mostValuable.Count > 5)
|
||||
mostValuable.Pop();
|
||||
});
|
||||
|
||||
shell.WriteLine($"Grid {gid} appraised to {value} credits.");
|
||||
shell.WriteLine($"The top most valuable items were:");
|
||||
foreach (var (price, ent) in mostValuable)
|
||||
{
|
||||
shell.WriteLine($"- {ToPrettyString(ent)} @ {price} credits");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateMobPrice(EntityUid uid, MobPriceComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(uid, out var body) || !TryComp<MobStateComponent>(uid, out var state))
|
||||
{
|
||||
Logger.ErrorS("pricing", $"Tried to get the mob price of {ToPrettyString(uid)}, which has no {nameof(BodyComponent)} and no {nameof(MobStateComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var partList = body.Slots.ToList();
|
||||
var totalPartsPresent = partList.Sum(x => x.Part != null ? 1 : 0);
|
||||
var totalParts = partList.Count;
|
||||
|
||||
var partRatio = totalPartsPresent / (double) totalParts;
|
||||
var partPenalty = component.Price * (1 - partRatio) * component.MissingBodyPartPenalty;
|
||||
|
||||
args.Price += (component.Price - partPenalty) * (state.IsAlive() ? 1.0 : component.DeathPenalty);
|
||||
}
|
||||
|
||||
private void CalculateStackPrice(EntityUid uid, StackPriceComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
if (!TryComp<StackComponent>(uid, out var stack))
|
||||
{
|
||||
Logger.ErrorS("pricing", $"Tried to get the stack price of {ToPrettyString(uid)}, which has no {nameof(StackComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
args.Price += stack.Count * component.Price;
|
||||
}
|
||||
|
||||
private void CalculateStaticPrice(EntityUid uid, StaticPriceComponent component, ref PriceCalculationEvent args)
|
||||
{
|
||||
args.Price += component.Price;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appraises an entity, returning it's price.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity to appraise.</param>
|
||||
/// <returns>The price of the entity.</returns>
|
||||
/// <remarks>
|
||||
/// This fires off an event to calculate the price.
|
||||
/// Calculating the price of an entity that somehow contains itself will likely hang.
|
||||
/// </remarks>
|
||||
public double GetPrice(EntityUid uid)
|
||||
{
|
||||
var ev = new PriceCalculationEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
//TODO: Add an OpaqueToAppraisal component or similar for blocking the recursive descent into containers, or preventing material pricing.
|
||||
|
||||
if (TryComp<MaterialComponent>(uid, out var material) && !HasComp<StackPriceComponent>(uid))
|
||||
{
|
||||
if (TryComp<StackComponent>(uid, out var stack))
|
||||
ev.Price += stack.Count * material.Materials.Sum(x => x.Price * material._materials[x.ID]);
|
||||
else
|
||||
ev.Price += material.Materials.Sum(x => x.Price);
|
||||
}
|
||||
|
||||
if (TryComp<ContainerManagerComponent>(uid, out var containers))
|
||||
{
|
||||
foreach (var container in containers.Containers)
|
||||
{
|
||||
foreach (var ent in container.Value.ContainedEntities)
|
||||
{
|
||||
ev.Price += GetPrice(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ev.Price;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appraises a grid, this is mainly meant to be used by yarrs.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to appraise.</param>
|
||||
/// <param name="predicate">An optional predicate that controls whether or not the entity is counted toward the total.</param>
|
||||
/// <param name="afterPredicate">An optional predicate to run after the price has been calculated. Useful for high scores or similar.</param>
|
||||
/// <returns>The total value of the grid.</returns>
|
||||
public double AppraiseGrid(EntityUid grid, Func<EntityUid, bool>? predicate = null, Action<EntityUid, double>? afterPredicate = null)
|
||||
{
|
||||
var xform = Transform(grid);
|
||||
var price = 0.0;
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
if (predicate is null || predicate(child))
|
||||
{
|
||||
var subPrice = GetPrice(child);
|
||||
price += subPrice;
|
||||
afterPredicate?.Invoke(child, subPrice);
|
||||
}
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A directed by-ref event fired on an entity when something needs to know it's price. This value is not cached.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct PriceCalculationEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The total price of the entity.
|
||||
/// </summary>
|
||||
public double Price = 0;
|
||||
|
||||
public PriceCalculationEvent() { }
|
||||
}
|
||||
216
Content.Server/GameTicking/Rules/PiratesRuleSystem.cs
Normal file
216
Content.Server/GameTicking/Rules/PiratesRuleSystem.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion.
|
||||
/// </summary>
|
||||
public sealed class PiratesRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IMapLoader _mapLoader = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||
|
||||
private List<Mind.Mind> _pirates = new();
|
||||
private EntityUid _pirateShip = EntityUid.Invalid;
|
||||
private double _initialShipValue;
|
||||
|
||||
public override string Prototype => "Pirates";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawningEvent);
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextEvent);
|
||||
}
|
||||
|
||||
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
if (Deleted(_pirateShip))
|
||||
{
|
||||
// Major loss, the ship somehow got annihilated.
|
||||
ev.AddLine(Loc.GetString("pirates-no-ship"));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
List<(double, EntityUid)> mostValuableThefts = new();
|
||||
|
||||
var finalValue = _pricingSystem.AppraiseGrid(_pirateShip, uid =>
|
||||
{
|
||||
foreach (var mind in _pirates)
|
||||
{
|
||||
if (mind.CurrentEntity == uid)
|
||||
return false; // Don't appraise the pirates twice, we count them in separately.
|
||||
}
|
||||
return true;
|
||||
}, (uid, price) =>
|
||||
{
|
||||
mostValuableThefts.Add((price, uid));
|
||||
mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
|
||||
if (mostValuableThefts.Count > 5)
|
||||
mostValuableThefts.Pop();
|
||||
});
|
||||
|
||||
foreach (var mind in _pirates)
|
||||
{
|
||||
if (mind.CurrentEntity is not null)
|
||||
finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value);
|
||||
}
|
||||
|
||||
var score = finalValue - _initialShipValue;
|
||||
|
||||
ev.AddLine(Loc.GetString("pirates-final-score", ("finalPrice", $"{finalValue:F2}"),
|
||||
("score", $"{score:F2}")));
|
||||
}
|
||||
|
||||
ev.AddLine(Loc.GetString("pirates-list-start"));
|
||||
foreach (var pirates in _pirates)
|
||||
{
|
||||
ev.AddLine($"- {pirates.CharacterName} ({pirates.Session?.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Started() { }
|
||||
|
||||
public override void Ended() { }
|
||||
|
||||
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
|
||||
{
|
||||
// Forgive me for copy-pasting nukies.
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
_pirates.Clear();
|
||||
|
||||
// Between 1 and <max pirate count>: needs at least n players per op.
|
||||
var numOps = Math.Max(1,
|
||||
(int)Math.Min(
|
||||
Math.Floor((double)ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)), _cfg.GetCVar(CCVars.PiratesMaxOps)));
|
||||
var ops = new IPlayerSession[numOps];
|
||||
for (var i = 0; i < numOps; i++)
|
||||
{
|
||||
ops[i] = _random.PickAndTake(ev.PlayerPool);
|
||||
}
|
||||
|
||||
var map = "/Maps/pirate.yml";
|
||||
|
||||
var aabbs = _stationSystem.Stations.SelectMany(x =>
|
||||
Comp<StationDataComponent>(x).Grids.Select(x => _mapManager.GetGridComp(x).Grid.WorldAABB)).ToArray();
|
||||
var aabb = aabbs[0];
|
||||
|
||||
for (var i = 1; i < aabbs.Length; i++)
|
||||
{
|
||||
aabb.Union(aabbs[i]);
|
||||
}
|
||||
|
||||
var (_, gridId) = _mapLoader.LoadBlueprint(GameTicker.DefaultMap, map, new MapLoadOptions
|
||||
{
|
||||
Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
|
||||
});
|
||||
|
||||
if (!gridId.HasValue)
|
||||
{
|
||||
Logger.ErrorS("pirates", $"Gridid was null when loading \"{map}\", aborting.");
|
||||
foreach (var session in ops)
|
||||
{
|
||||
ev.PlayerPool.Add(session);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_pirateShip = _mapManager.GetGridEuid(gridId.Value);
|
||||
|
||||
// TODO: Loot table or something
|
||||
var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR
|
||||
|
||||
var spawns = new List<EntityCoordinates>();
|
||||
|
||||
// Forgive me for hardcoding prototypes
|
||||
foreach (var (_, meta, xform) in EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
|
||||
{
|
||||
if (meta.EntityPrototype?.ID != "SpawnPointPirates" || xform.ParentUid != _pirateShip) continue;
|
||||
|
||||
spawns.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
if (spawns.Count == 0)
|
||||
{
|
||||
spawns.Add(Transform(_pirateShip).Coordinates);
|
||||
Logger.WarningS("pirates", $"Fell back to default spawn for nukies!");
|
||||
}
|
||||
|
||||
for (var i = 0; i < ops.Length; i++)
|
||||
{
|
||||
var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
|
||||
|
||||
var name = sex.GetName("Human", _prototypeManager, _random);
|
||||
|
||||
var session = ops[i];
|
||||
var newMind = new Mind.Mind(session.UserId)
|
||||
{
|
||||
CharacterName = name
|
||||
};
|
||||
newMind.ChangeOwningPlayer(session.UserId);
|
||||
|
||||
var mob = Spawn("MobHuman", _random.Pick(spawns));
|
||||
MetaData(mob).EntityName = name;
|
||||
|
||||
newMind.TransferTo(mob);
|
||||
_stationSpawningSystem.EquipStartingGear(mob, pirateGear, null);
|
||||
|
||||
_pirates.Add(newMind);
|
||||
|
||||
GameTicker.PlayerJoinGame(session);
|
||||
}
|
||||
|
||||
_initialShipValue = _pricingSystem.AppraiseGrid(_pirateShip); // Include the players in the appraisal.
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.Players.Length == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,6 +286,19 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<int> NukeopsPlayersPerOp =
|
||||
CVarDef.Create("nukeops.players_per_op", 5);
|
||||
|
||||
/*
|
||||
* Pirates
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<int> PiratesMinPlayers =
|
||||
CVarDef.Create("pirates.min_players", 25);
|
||||
|
||||
public static readonly CVarDef<int> PiratesMaxOps =
|
||||
CVarDef.Create("pirates.max_pirates", 6);
|
||||
|
||||
public static readonly CVarDef<int> PiratesPlayersPerOp =
|
||||
CVarDef.Create("pirates.players_per_pirate", 5);
|
||||
|
||||
/*
|
||||
* Console
|
||||
*/
|
||||
|
||||
@@ -42,5 +42,11 @@ namespace Content.Shared.Materials
|
||||
[ViewVariables]
|
||||
[DataField("icon")]
|
||||
public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// The price per cm3.
|
||||
/// </summary>
|
||||
[DataField("price", required: true)]
|
||||
public double Price = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
price-gun-pricing-result = The device deems {THE($object)} to be worth {$price} credits.
|
||||
@@ -0,0 +1,7 @@
|
||||
pirates-title = Privateers
|
||||
pirates-description = A group of privateers has approached your lowly station. Hostile or not, their sole goal is to end the round with as many knicknacks on their ship as they can get.
|
||||
|
||||
pirates-no-ship = Through unknown circumstances, the privateer's ship was completely and utterly destroyed. No score.
|
||||
pirates-final-score = The privateers successfully obtained {$score} credits worth of knicknacks, with their vessel being worth {$finalPrice} by the end.
|
||||
pirates-list-start = The privateers were:
|
||||
pirates-most-valuable = The most valuable stolen items were:
|
||||
@@ -254,6 +254,8 @@
|
||||
resistance: 0.7
|
||||
- type: ToggleableClothing
|
||||
clothingPrototype: ClothingHeadHelmetHardsuitRd
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterHardsuitBase
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
sprintModifier: 1
|
||||
enabled: false
|
||||
- type: NoSlip
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
parent: ClothingShoesBase
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
- type: entity
|
||||
id: SpawnPointPirates
|
||||
parent: MarkerBase
|
||||
name: Pirate spawn point
|
||||
components:
|
||||
- type: SpawnPoint
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: green
|
||||
- sprite: Objects/Fun/toys.rsi
|
||||
state: synb
|
||||
@@ -142,6 +142,8 @@
|
||||
60: 0.6
|
||||
80: 0.4
|
||||
90: 0.2
|
||||
- type: MobPrice
|
||||
price: 1000 # Living critters are valuable in space.
|
||||
|
||||
- type: entity
|
||||
save: false
|
||||
@@ -195,3 +197,4 @@
|
||||
Heat : 1 #per second, scales with temperature & other constants
|
||||
- type: Bloodstream
|
||||
bloodMaxVolume: 150
|
||||
|
||||
|
||||
@@ -322,6 +322,9 @@
|
||||
attributes:
|
||||
proper: true
|
||||
- type: StandingState
|
||||
- type: MobPrice
|
||||
price: 1500 # Kidnapping a living person and selling them for cred is a good move.
|
||||
deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less.
|
||||
|
||||
- type: entity
|
||||
save: false
|
||||
|
||||
@@ -177,6 +177,8 @@
|
||||
reagents:
|
||||
- ReagentId: Bicaridine
|
||||
Quantity: 20
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
name: raw crab meat
|
||||
|
||||
@@ -12,3 +12,5 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- DroneUsable
|
||||
- type: StaticPrice
|
||||
price: 75
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- DroneUsable
|
||||
- type: StaticPrice
|
||||
price: 75
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
@@ -56,6 +58,8 @@
|
||||
components:
|
||||
- type: ComputerBoard
|
||||
prototype: ComputerSupplyOrdering
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
@@ -127,6 +131,8 @@
|
||||
components:
|
||||
- type: ComputerBoard
|
||||
prototype: ComputerId
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
interfaces:
|
||||
- key: enum.NukeUiKey.Key
|
||||
type: NukeBoundUserInterface
|
||||
- type: StaticPrice # TODO: Make absolutely certain cargo cannot sell this, that'd be horrible. Presumably, add an export ban component so only the yarrs get a deal here.
|
||||
price: 50000 # YOU STOLE A NUCLEAR FISSION EXPLOSIVE?!
|
||||
|
||||
- type: entity
|
||||
parent: NuclearBomb
|
||||
@@ -87,3 +89,5 @@
|
||||
Quantity: 3000
|
||||
- type: ReagentTank
|
||||
transferAmount: 100
|
||||
- type: StaticPrice
|
||||
price: 5000 # That's a pretty fancy keg you got there.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
sprite: Objects/Materials/Sheets/glass.rsi
|
||||
- type: Item
|
||||
sprite: Objects/Materials/Sheets/glass.rsi
|
||||
- type: StaticPrice
|
||||
price: 0
|
||||
- type: ItemStatus
|
||||
- type: Tag
|
||||
tags:
|
||||
@@ -49,6 +51,8 @@
|
||||
- type: Material
|
||||
materials:
|
||||
Glass: 100
|
||||
- type: StackPrice
|
||||
price: 5
|
||||
- type: Stack
|
||||
stackType: Glass
|
||||
- type: Sprite
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
sprite: Objects/Materials/Sheets/metal.rsi
|
||||
- type: Item
|
||||
sprite: Objects/Materials/Sheets/metal.rsi
|
||||
- type: StaticPrice
|
||||
price: 0
|
||||
- type: ItemStatus
|
||||
- type: Tag
|
||||
tags:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
sprite: Objects/Materials/ingots.rsi
|
||||
- type: Item
|
||||
sprite: Objects/Materials/ingots.rsi
|
||||
- type: StaticPrice
|
||||
price: 0
|
||||
- type: ItemStatus
|
||||
- type: Tag
|
||||
tags:
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
size: 12
|
||||
sprite: Objects/Misc/nukedisk.rsi
|
||||
state: icon
|
||||
- type: StaticPrice
|
||||
price: 2000
|
||||
|
||||
- type: entity
|
||||
name: nuclear authentication disk
|
||||
@@ -29,3 +31,5 @@
|
||||
size: 12
|
||||
sprite: Objects/Misc/nukedisk.rsi
|
||||
state: icon
|
||||
- type: StaticPrice
|
||||
price: 1 # it's worth even less than normal items. Perfection.
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
components:
|
||||
- type: Material
|
||||
materials:
|
||||
Credit: 100
|
||||
Credit: 1
|
||||
- type: StaticPrice
|
||||
price: 0
|
||||
- type: Stack
|
||||
stackType: Credit
|
||||
max: 1000000 # if you somehow get this rich consider buying a second station
|
||||
@@ -45,6 +47,7 @@
|
||||
name: credit
|
||||
stack: Credit
|
||||
icon: /Textures/Objects/Economy/cash.rsi/cash.png
|
||||
price: 1
|
||||
|
||||
- type: stack
|
||||
id: Credit
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
- type: ExaminableSolution
|
||||
solution: hypospray
|
||||
- type: Hypospray
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
name: gorlax hypospray
|
||||
@@ -75,6 +77,8 @@
|
||||
- Trash
|
||||
- type: Recyclable
|
||||
- type: SpaceGarbage
|
||||
- type: StaticPrice
|
||||
price: 75 # These are limited supply items.
|
||||
|
||||
- type: entity
|
||||
name: emergency medipen
|
||||
@@ -166,4 +170,5 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Write
|
||||
|
||||
- type: StaticPrice
|
||||
price: 75
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
max: 999999 # todo: add support for unlimited stacks
|
||||
stackType: Telecrystal
|
||||
- type: Telecrystal
|
||||
- type: StackPrice
|
||||
price: 200
|
||||
|
||||
- type: entity
|
||||
parent: Telecrystal
|
||||
|
||||
15
Resources/Prototypes/Entities/Objects/Tools/appraisal.yml
Normal file
15
Resources/Prototypes/Entities/Objects/Tools/appraisal.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: AppraisalTool
|
||||
name: appraisal tool
|
||||
description: A beancounter's best friend, with a quantum connection to the galactic market and the ability to appraise even the toughest items.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Tools/appraisal-tool.rsi
|
||||
state: icon
|
||||
netsync: false
|
||||
- type: Item
|
||||
sprite: Objects/Tools/appraisal-tool.rsi
|
||||
- type: PriceGun
|
||||
- type: UseDelay
|
||||
delay: 3
|
||||
@@ -22,6 +22,8 @@
|
||||
sprite: Objects/Tools/cable-coils.rsi
|
||||
- type: CablePlacer
|
||||
- type: Clickable
|
||||
- type: StackPrice
|
||||
price: 0.5
|
||||
|
||||
- type: entity
|
||||
id: CableHVStack
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
tags:
|
||||
- VehicleKey
|
||||
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
|
||||
- type: StaticPrice
|
||||
price: 750 # Grand Theft Auto.
|
||||
|
||||
- type: entity
|
||||
id: VehiclePussyWagon
|
||||
|
||||
@@ -313,3 +313,5 @@
|
||||
magState: mag
|
||||
steps: 5
|
||||
zeroVisible: true
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
components:
|
||||
- type: Item
|
||||
size: 5
|
||||
- type: StaticPrice
|
||||
price: 5
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: MovedByPressure
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
components:
|
||||
- Paper
|
||||
|
||||
|
||||
- type: entity
|
||||
id: CrateBaseSecure
|
||||
parent: BaseStructureDynamic
|
||||
|
||||
@@ -48,3 +48,5 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: StaticPrice
|
||||
price: 0.5
|
||||
|
||||
@@ -366,6 +366,8 @@
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: ReinforcedWallVisualizer
|
||||
- type: StaticPrice
|
||||
price: 41.5 # total material cost. If you change the recipe for the wall you should recalculate this.
|
||||
|
||||
# Riveting
|
||||
- type: entity
|
||||
@@ -585,6 +587,8 @@
|
||||
- type: IconSmooth
|
||||
key: walls
|
||||
base: solid
|
||||
- type: StaticPrice
|
||||
price: 1 # total material cost. If you change the recipe for the wall you should recalculate this.
|
||||
|
||||
- type: entity
|
||||
parent: WallBase
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: StaticPrice
|
||||
price: 20.5
|
||||
|
||||
- type: entity
|
||||
id: PlasmaWindowDirectional
|
||||
@@ -77,3 +79,5 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: StaticPrice
|
||||
price: 20.5
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: StaticPrice
|
||||
price: 0.75
|
||||
|
||||
- type: entity
|
||||
parent: ReinforcedWindow
|
||||
@@ -68,6 +70,8 @@
|
||||
- type: Occluder
|
||||
sizeX: 32
|
||||
sizeY: 32
|
||||
- type: StaticPrice
|
||||
price: 0.75
|
||||
|
||||
- type: entity
|
||||
id: WindowReinforcedDirectional
|
||||
@@ -107,3 +111,5 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: StaticPrice
|
||||
price: 0.75
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: StaticPrice
|
||||
price: 20.75
|
||||
|
||||
- type: entity
|
||||
id: PlasmaReinforcedWindowDirectional
|
||||
@@ -95,3 +97,5 @@
|
||||
max: 2
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: StaticPrice
|
||||
price: 20.75
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
trackAllDamage: true
|
||||
damageOverlay:
|
||||
sprite: Structures/Windows/cracks.rsi
|
||||
- type: StaticPrice
|
||||
price: 0.5
|
||||
|
||||
- type: entity
|
||||
id: WindowDirectional
|
||||
@@ -161,6 +163,8 @@
|
||||
- type: Construction
|
||||
graph: WindowDirectional
|
||||
node: windowDirectional
|
||||
- type: StaticPrice
|
||||
price: 0.5
|
||||
|
||||
- type: entity
|
||||
id: WindowTintedDirectional
|
||||
@@ -184,6 +188,8 @@
|
||||
state: tinted_window
|
||||
- type: Occluder
|
||||
boundingBox: "-0.5,-0.5,0.5,-0.3"
|
||||
- type: StaticPrice
|
||||
price: 0.5
|
||||
|
||||
- type: entity
|
||||
id: WindowFrostedDirectional
|
||||
@@ -202,3 +208,5 @@
|
||||
- type: Icon
|
||||
sprite: Structures/Windows/directional.rsi
|
||||
state: frosted_window
|
||||
- type: StaticPrice
|
||||
price: 0.5
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
name: glass
|
||||
icon: Objects/Materials/Sheets/glass.rsi/glass.png
|
||||
color: "#a8ccd7"
|
||||
price: 0.0025
|
||||
|
||||
- type: material
|
||||
id: ReinforcedGlass
|
||||
@@ -11,6 +12,7 @@
|
||||
name: reinforced glass
|
||||
icon: Objects/Materials/Sheets/glass.rsi/rglass.png
|
||||
color: "#549bb0"
|
||||
price: 0.00375 # 2-1 mix of glass and metal.
|
||||
|
||||
- type: material
|
||||
id: PlasmaGlass
|
||||
@@ -18,6 +20,7 @@
|
||||
name: plasma glass
|
||||
icon: Objects/Materials/Sheets/glass.rsi/pglass.png
|
||||
color: "#b35989"
|
||||
price: 0.1025 # 1-1 mix of plasma and glass.
|
||||
|
||||
- type: material
|
||||
id: ReinforcedPlasmaGlass
|
||||
@@ -25,3 +28,4 @@
|
||||
name: reinforced plasma glass
|
||||
icon: Objects/Materials/Sheets/glass.rsi/rpglass.png
|
||||
color: "#8c4069"
|
||||
price: 0.10375 # 2-2-1 mix of plasma, glass, and metal.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
name: cloth
|
||||
icon: /Textures/Objects/Materials/materials.rsi/cloth.png
|
||||
color: "#e7e7de"
|
||||
price: 0.005
|
||||
|
||||
- type: material
|
||||
id: Durathread
|
||||
@@ -12,6 +13,7 @@
|
||||
name: durathread
|
||||
icon: /Textures/Objects/Materials/materials.rsi/durathread.png
|
||||
color: "#8291a1"
|
||||
price: 0.0175 # 1-1 mix of plastic and cloth.
|
||||
|
||||
- type: material
|
||||
id: Plasma
|
||||
@@ -19,6 +21,7 @@
|
||||
name: plasma
|
||||
icon: Objects/Materials/Sheets/other.rsi/plasma.png
|
||||
color: "#7e009e"
|
||||
price: 0.1
|
||||
|
||||
- type: material
|
||||
id: Plastic
|
||||
@@ -26,6 +29,7 @@
|
||||
name: plastic
|
||||
icon: Objects/Materials/Sheets/other.rsi/plastic.png
|
||||
color: "#d9d9d9"
|
||||
price: 0.0125
|
||||
|
||||
- type: material
|
||||
id: Wood
|
||||
@@ -33,6 +37,7 @@
|
||||
name: wood
|
||||
icon: Objects/Materials/materials.rsi/wood.png
|
||||
color: "#966F33"
|
||||
price: 0.01
|
||||
|
||||
- type: material
|
||||
id: Uranium
|
||||
@@ -40,3 +45,4 @@
|
||||
name: uranium
|
||||
icon: Objects/Materials/Sheets/other.rsi/uranium.png
|
||||
color: "#32a852"
|
||||
price: 0.05
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
stack: Steel
|
||||
name: steel
|
||||
icon: Objects/Materials/Sheets/metal.rsi/steel.png
|
||||
price: 0.0025
|
||||
|
||||
- type: material
|
||||
id: Gold
|
||||
@@ -10,6 +11,7 @@
|
||||
name: gold
|
||||
icon: Objects/Materials/ingots.rsi/gold.png
|
||||
color: "#FFD700"
|
||||
price: 0.0625
|
||||
|
||||
- type: material
|
||||
id: Silver
|
||||
@@ -17,6 +19,7 @@
|
||||
name: silver
|
||||
icon: Objects/Materials/ingots.rsi/silver.png
|
||||
color: "#C0C0C0"
|
||||
price: 0.025
|
||||
|
||||
- type: material
|
||||
id: Plasteel
|
||||
@@ -24,3 +27,4 @@
|
||||
name: plasteel
|
||||
icon: Objects/Materials/Sheets/metal.rsi/plasteel.png
|
||||
color: "#696969" #Okay, this is epic
|
||||
price: 0.1025 # 1-1 mix of plasma and steel.
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
shoes: ClothingShoesBootsWork
|
||||
id: PassengerPDA
|
||||
belt: ClothingBeltUtility
|
||||
pocket1: AppraisalTool
|
||||
innerclothingskirt: ClothingUniformJumpskirtColorLightBrown
|
||||
satchel: ClothingBackpackSatchelEngineering
|
||||
duffelbag: ClothingBackpackSatchelEngineering
|
||||
|
||||
@@ -79,3 +79,13 @@
|
||||
showInVote: false
|
||||
rules:
|
||||
- Nukeops
|
||||
|
||||
- type: gamePreset
|
||||
id: Pirates
|
||||
alias:
|
||||
- pirates
|
||||
name: pirates-title
|
||||
description: pirates-description
|
||||
showInVote: false
|
||||
rules:
|
||||
- Pirates
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
- type: gameRule
|
||||
id: Nukeops
|
||||
|
||||
- type: gameRule
|
||||
id: Pirates
|
||||
|
||||
- type: gameRule
|
||||
id: Suspicion
|
||||
|
||||
|
||||
BIN
Resources/Textures/Objects/Tools/appraisal-tool.rsi/icon.png
Normal file
BIN
Resources/Textures/Objects/Tools/appraisal-tool.rsi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"copyright" : "Taken from https://github.com/tgstation/tgstation/blob/master/icons/obj/device.dmi state export_scanner at commit 7544e865d0771d9bd917461e9da16d301fe40fc3.",
|
||||
"license" : "CC-BY-SA-3.0",
|
||||
"size" : {
|
||||
"x" : 32,
|
||||
"y" : 32
|
||||
},
|
||||
"states" : [
|
||||
{
|
||||
"name" : "icon"
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
}
|
||||
Reference in New Issue
Block a user