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:
Moony
2022-06-03 10:56:11 -05:00
committed by GitHub
parent c87f4ab65c
commit fada213a22
53 changed files with 712 additions and 26 deletions

View File

@@ -1,3 +1,4 @@
using Content.Server.Cargo.Systems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.Cargo; using Content.Shared.Cargo;

View File

@@ -1,3 +1,4 @@
using Content.Server.Cargo.Systems;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
namespace Content.Server.Cargo.Components namespace Content.Server.Cargo.Components

View File

@@ -1,3 +1,4 @@
using Content.Server.Cargo.Systems;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
using Content.Shared.MachineLinking; using Content.Shared.MachineLinking;

View 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;
}

View 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
{
}

View 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;
}

View 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;
}

View File

@@ -7,7 +7,7 @@ using Content.Shared.Access.Systems;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
namespace Content.Server.Cargo namespace Content.Server.Cargo.Systems
{ {
public sealed partial class CargoSystem public sealed partial class CargoSystem
{ {

View File

@@ -6,7 +6,7 @@ using Content.Shared.Cargo;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player; using Robust.Shared.Player;
namespace Content.Server.Cargo; namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem public sealed partial class CargoSystem
{ {

View File

@@ -2,7 +2,7 @@ using Content.Shared.Cargo;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Cargo; namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem : SharedCargoSystem public sealed partial class CargoSystem : SharedCargoSystem
{ {

View 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);
}
}

View 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() { }
}

View 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;
}
}
}

View File

@@ -286,6 +286,19 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<int> NukeopsPlayersPerOp = public static readonly CVarDef<int> NukeopsPlayersPerOp =
CVarDef.Create("nukeops.players_per_op", 5); 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 * Console
*/ */

View File

@@ -42,5 +42,11 @@ namespace Content.Shared.Materials
[ViewVariables] [ViewVariables]
[DataField("icon")] [DataField("icon")]
public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid; public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid;
/// <summary>
/// The price per cm3.
/// </summary>
[DataField("price", required: true)]
public double Price = 0;
} }
} }

View File

@@ -0,0 +1 @@
price-gun-pricing-result = The device deems {THE($object)} to be worth {$price} credits.

View File

@@ -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:

View File

@@ -254,6 +254,8 @@
resistance: 0.7 resistance: 0.7
- type: ToggleableClothing - type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitRd clothingPrototype: ClothingHeadHelmetHardsuitRd
- type: StaticPrice
price: 750
- type: entity - type: entity
parent: ClothingOuterHardsuitBase parent: ClothingOuterHardsuitBase

View File

@@ -46,6 +46,8 @@
sprintModifier: 1 sprintModifier: 1
enabled: false enabled: false
- type: NoSlip - type: NoSlip
- type: StaticPrice
price: 750
- type: entity - type: entity
parent: ClothingShoesBase parent: ClothingShoesBase

View File

@@ -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

View File

@@ -142,6 +142,8 @@
60: 0.6 60: 0.6
80: 0.4 80: 0.4
90: 0.2 90: 0.2
- type: MobPrice
price: 1000 # Living critters are valuable in space.
- type: entity - type: entity
save: false save: false
@@ -195,3 +197,4 @@
Heat : 1 #per second, scales with temperature & other constants Heat : 1 #per second, scales with temperature & other constants
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 150 bloodMaxVolume: 150

View File

@@ -322,6 +322,9 @@
attributes: attributes:
proper: true proper: true
- type: StandingState - 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 - type: entity
save: false save: false

View File

@@ -177,6 +177,8 @@
reagents: reagents:
- ReagentId: Bicaridine - ReagentId: Bicaridine
Quantity: 20 Quantity: 20
- type: StaticPrice
price: 750
- type: entity - type: entity
name: raw crab meat name: raw crab meat

View File

@@ -12,3 +12,5 @@
- type: Tag - type: Tag
tags: tags:
- DroneUsable - DroneUsable
- type: StaticPrice
price: 75

View File

@@ -11,6 +11,8 @@
- type: Tag - type: Tag
tags: tags:
- DroneUsable - DroneUsable
- type: StaticPrice
price: 75
- type: entity - type: entity
parent: BaseComputerCircuitboard parent: BaseComputerCircuitboard
@@ -56,6 +58,8 @@
components: components:
- type: ComputerBoard - type: ComputerBoard
prototype: ComputerSupplyOrdering prototype: ComputerSupplyOrdering
- type: StaticPrice
price: 750
- type: entity - type: entity
parent: BaseComputerCircuitboard parent: BaseComputerCircuitboard
@@ -127,6 +131,8 @@
components: components:
- type: ComputerBoard - type: ComputerBoard
prototype: ComputerId prototype: ComputerId
- type: StaticPrice
price: 750
- type: entity - type: entity
parent: BaseComputerCircuitboard parent: BaseComputerCircuitboard

View File

@@ -47,6 +47,8 @@
interfaces: interfaces:
- key: enum.NukeUiKey.Key - key: enum.NukeUiKey.Key
type: NukeBoundUserInterface 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 - type: entity
parent: NuclearBomb parent: NuclearBomb
@@ -87,3 +89,5 @@
Quantity: 3000 Quantity: 3000
- type: ReagentTank - type: ReagentTank
transferAmount: 100 transferAmount: 100
- type: StaticPrice
price: 5000 # That's a pretty fancy keg you got there.

View File

@@ -9,6 +9,8 @@
sprite: Objects/Materials/Sheets/glass.rsi sprite: Objects/Materials/Sheets/glass.rsi
- type: Item - type: Item
sprite: Objects/Materials/Sheets/glass.rsi sprite: Objects/Materials/Sheets/glass.rsi
- type: StaticPrice
price: 0
- type: ItemStatus - type: ItemStatus
- type: Tag - type: Tag
tags: tags:
@@ -49,6 +51,8 @@
- type: Material - type: Material
materials: materials:
Glass: 100 Glass: 100
- type: StackPrice
price: 5
- type: Stack - type: Stack
stackType: Glass stackType: Glass
- type: Sprite - type: Sprite

View File

@@ -9,6 +9,8 @@
sprite: Objects/Materials/Sheets/metal.rsi sprite: Objects/Materials/Sheets/metal.rsi
- type: Item - type: Item
sprite: Objects/Materials/Sheets/metal.rsi sprite: Objects/Materials/Sheets/metal.rsi
- type: StaticPrice
price: 0
- type: ItemStatus - type: ItemStatus
- type: Tag - type: Tag
tags: tags:

View File

@@ -9,6 +9,8 @@
sprite: Objects/Materials/ingots.rsi sprite: Objects/Materials/ingots.rsi
- type: Item - type: Item
sprite: Objects/Materials/ingots.rsi sprite: Objects/Materials/ingots.rsi
- type: StaticPrice
price: 0
- type: ItemStatus - type: ItemStatus
- type: Tag - type: Tag
tags: tags:

View File

@@ -13,6 +13,8 @@
size: 12 size: 12
sprite: Objects/Misc/nukedisk.rsi sprite: Objects/Misc/nukedisk.rsi
state: icon state: icon
- type: StaticPrice
price: 2000
- type: entity - type: entity
name: nuclear authentication disk name: nuclear authentication disk
@@ -29,3 +31,5 @@
size: 12 size: 12
sprite: Objects/Misc/nukedisk.rsi sprite: Objects/Misc/nukedisk.rsi
state: icon state: icon
- type: StaticPrice
price: 1 # it's worth even less than normal items. Perfection.

View File

@@ -6,7 +6,9 @@
components: components:
- type: Material - type: Material
materials: materials:
Credit: 100 Credit: 1
- type: StaticPrice
price: 0
- type: Stack - type: Stack
stackType: Credit stackType: Credit
max: 1000000 # if you somehow get this rich consider buying a second station max: 1000000 # if you somehow get this rich consider buying a second station
@@ -45,6 +47,7 @@
name: credit name: credit
stack: Credit stack: Credit
icon: /Textures/Objects/Economy/cash.rsi/cash.png icon: /Textures/Objects/Economy/cash.rsi/cash.png
price: 1
- type: stack - type: stack
id: Credit id: Credit

View File

@@ -18,6 +18,8 @@
- type: ExaminableSolution - type: ExaminableSolution
solution: hypospray solution: hypospray
- type: Hypospray - type: Hypospray
- type: StaticPrice
price: 750
- type: entity - type: entity
name: gorlax hypospray name: gorlax hypospray
@@ -75,6 +77,8 @@
- Trash - Trash
- type: Recyclable - type: Recyclable
- type: SpaceGarbage - type: SpaceGarbage
- type: StaticPrice
price: 75 # These are limited supply items.
- type: entity - type: entity
name: emergency medipen name: emergency medipen
@@ -166,4 +170,5 @@
- type: Tag - type: Tag
tags: tags:
- Write - Write
- type: StaticPrice
price: 75

View File

@@ -16,6 +16,8 @@
max: 999999 # todo: add support for unlimited stacks max: 999999 # todo: add support for unlimited stacks
stackType: Telecrystal stackType: Telecrystal
- type: Telecrystal - type: Telecrystal
- type: StackPrice
price: 200
- type: entity - type: entity
parent: Telecrystal parent: Telecrystal

View 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

View File

@@ -22,6 +22,8 @@
sprite: Objects/Tools/cable-coils.rsi sprite: Objects/Tools/cable-coils.rsi
- type: CablePlacer - type: CablePlacer
- type: Clickable - type: Clickable
- type: StackPrice
price: 0.5
- type: entity - type: entity
id: CableHVStack id: CableHVStack

View File

@@ -63,6 +63,8 @@
tags: tags:
- VehicleKey - VehicleKey
insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg insertSound: /Audio/Effects/Vehicle/vehiclestartup.ogg
- type: StaticPrice
price: 750 # Grand Theft Auto.
- type: entity - type: entity
id: VehiclePussyWagon id: VehiclePussyWagon

View File

@@ -313,3 +313,5 @@
magState: mag magState: mag
steps: 5 steps: 5
zeroVisible: true zeroVisible: true
- type: StaticPrice
price: 750

View File

@@ -5,6 +5,8 @@
components: components:
- type: Item - type: Item
size: 5 size: 5
- type: StaticPrice
price: 5
- type: Clickable - type: Clickable
- type: InteractionOutline - type: InteractionOutline
- type: MovedByPressure - type: MovedByPressure

View File

@@ -58,7 +58,6 @@
components: components:
- Paper - Paper
- type: entity - type: entity
id: CrateBaseSecure id: CrateBaseSecure
parent: BaseStructureDynamic parent: BaseStructureDynamic

View File

@@ -48,3 +48,5 @@
max: 1 max: 1
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: StaticPrice
price: 0.5

View File

@@ -366,6 +366,8 @@
- type: Appearance - type: Appearance
visuals: visuals:
- type: ReinforcedWallVisualizer - type: ReinforcedWallVisualizer
- type: StaticPrice
price: 41.5 # total material cost. If you change the recipe for the wall you should recalculate this.
# Riveting # Riveting
- type: entity - type: entity
@@ -585,6 +587,8 @@
- type: IconSmooth - type: IconSmooth
key: walls key: walls
base: solid base: solid
- type: StaticPrice
price: 1 # total material cost. If you change the recipe for the wall you should recalculate this.
- type: entity - type: entity
parent: WallBase parent: WallBase

View File

@@ -40,6 +40,8 @@
trackAllDamage: true trackAllDamage: true
damageOverlay: damageOverlay:
sprite: Structures/Windows/cracks.rsi sprite: Structures/Windows/cracks.rsi
- type: StaticPrice
price: 20.5
- type: entity - type: entity
id: PlasmaWindowDirectional id: PlasmaWindowDirectional
@@ -77,3 +79,5 @@
max: 2 max: 2
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: StaticPrice
price: 20.5

View File

@@ -49,6 +49,8 @@
trackAllDamage: true trackAllDamage: true
damageOverlay: damageOverlay:
sprite: Structures/Windows/cracks.rsi sprite: Structures/Windows/cracks.rsi
- type: StaticPrice
price: 0.75
- type: entity - type: entity
parent: ReinforcedWindow parent: ReinforcedWindow
@@ -68,6 +70,8 @@
- type: Occluder - type: Occluder
sizeX: 32 sizeX: 32
sizeY: 32 sizeY: 32
- type: StaticPrice
price: 0.75
- type: entity - type: entity
id: WindowReinforcedDirectional id: WindowReinforcedDirectional
@@ -107,3 +111,5 @@
max: 2 max: 2
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: StaticPrice
price: 0.75

View File

@@ -49,6 +49,8 @@
trackAllDamage: true trackAllDamage: true
damageOverlay: damageOverlay:
sprite: Structures/Windows/cracks.rsi sprite: Structures/Windows/cracks.rsi
- type: StaticPrice
price: 20.75
- type: entity - type: entity
id: PlasmaReinforcedWindowDirectional id: PlasmaReinforcedWindowDirectional
@@ -95,3 +97,5 @@
max: 2 max: 2
- !type:DoActsBehavior - !type:DoActsBehavior
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: StaticPrice
price: 20.75

View File

@@ -85,6 +85,8 @@
trackAllDamage: true trackAllDamage: true
damageOverlay: damageOverlay:
sprite: Structures/Windows/cracks.rsi sprite: Structures/Windows/cracks.rsi
- type: StaticPrice
price: 0.5
- type: entity - type: entity
id: WindowDirectional id: WindowDirectional
@@ -161,6 +163,8 @@
- type: Construction - type: Construction
graph: WindowDirectional graph: WindowDirectional
node: windowDirectional node: windowDirectional
- type: StaticPrice
price: 0.5
- type: entity - type: entity
id: WindowTintedDirectional id: WindowTintedDirectional
@@ -184,6 +188,8 @@
state: tinted_window state: tinted_window
- type: Occluder - type: Occluder
boundingBox: "-0.5,-0.5,0.5,-0.3" boundingBox: "-0.5,-0.5,0.5,-0.3"
- type: StaticPrice
price: 0.5
- type: entity - type: entity
id: WindowFrostedDirectional id: WindowFrostedDirectional
@@ -202,3 +208,5 @@
- type: Icon - type: Icon
sprite: Structures/Windows/directional.rsi sprite: Structures/Windows/directional.rsi
state: frosted_window state: frosted_window
- type: StaticPrice
price: 0.5

View File

@@ -4,6 +4,7 @@
name: glass name: glass
icon: Objects/Materials/Sheets/glass.rsi/glass.png icon: Objects/Materials/Sheets/glass.rsi/glass.png
color: "#a8ccd7" color: "#a8ccd7"
price: 0.0025
- type: material - type: material
id: ReinforcedGlass id: ReinforcedGlass
@@ -11,6 +12,7 @@
name: reinforced glass name: reinforced glass
icon: Objects/Materials/Sheets/glass.rsi/rglass.png icon: Objects/Materials/Sheets/glass.rsi/rglass.png
color: "#549bb0" color: "#549bb0"
price: 0.00375 # 2-1 mix of glass and metal.
- type: material - type: material
id: PlasmaGlass id: PlasmaGlass
@@ -18,6 +20,7 @@
name: plasma glass name: plasma glass
icon: Objects/Materials/Sheets/glass.rsi/pglass.png icon: Objects/Materials/Sheets/glass.rsi/pglass.png
color: "#b35989" color: "#b35989"
price: 0.1025 # 1-1 mix of plasma and glass.
- type: material - type: material
id: ReinforcedPlasmaGlass id: ReinforcedPlasmaGlass
@@ -25,3 +28,4 @@
name: reinforced plasma glass name: reinforced plasma glass
icon: Objects/Materials/Sheets/glass.rsi/rpglass.png icon: Objects/Materials/Sheets/glass.rsi/rpglass.png
color: "#8c4069" color: "#8c4069"
price: 0.10375 # 2-2-1 mix of plasma, glass, and metal.

View File

@@ -5,6 +5,7 @@
name: cloth name: cloth
icon: /Textures/Objects/Materials/materials.rsi/cloth.png icon: /Textures/Objects/Materials/materials.rsi/cloth.png
color: "#e7e7de" color: "#e7e7de"
price: 0.005
- type: material - type: material
id: Durathread id: Durathread
@@ -12,6 +13,7 @@
name: durathread name: durathread
icon: /Textures/Objects/Materials/materials.rsi/durathread.png icon: /Textures/Objects/Materials/materials.rsi/durathread.png
color: "#8291a1" color: "#8291a1"
price: 0.0175 # 1-1 mix of plastic and cloth.
- type: material - type: material
id: Plasma id: Plasma
@@ -19,6 +21,7 @@
name: plasma name: plasma
icon: Objects/Materials/Sheets/other.rsi/plasma.png icon: Objects/Materials/Sheets/other.rsi/plasma.png
color: "#7e009e" color: "#7e009e"
price: 0.1
- type: material - type: material
id: Plastic id: Plastic
@@ -26,6 +29,7 @@
name: plastic name: plastic
icon: Objects/Materials/Sheets/other.rsi/plastic.png icon: Objects/Materials/Sheets/other.rsi/plastic.png
color: "#d9d9d9" color: "#d9d9d9"
price: 0.0125
- type: material - type: material
id: Wood id: Wood
@@ -33,6 +37,7 @@
name: wood name: wood
icon: Objects/Materials/materials.rsi/wood.png icon: Objects/Materials/materials.rsi/wood.png
color: "#966F33" color: "#966F33"
price: 0.01
- type: material - type: material
id: Uranium id: Uranium
@@ -40,3 +45,4 @@
name: uranium name: uranium
icon: Objects/Materials/Sheets/other.rsi/uranium.png icon: Objects/Materials/Sheets/other.rsi/uranium.png
color: "#32a852" color: "#32a852"
price: 0.05

View File

@@ -3,6 +3,7 @@
stack: Steel stack: Steel
name: steel name: steel
icon: Objects/Materials/Sheets/metal.rsi/steel.png icon: Objects/Materials/Sheets/metal.rsi/steel.png
price: 0.0025
- type: material - type: material
id: Gold id: Gold
@@ -10,6 +11,7 @@
name: gold name: gold
icon: Objects/Materials/ingots.rsi/gold.png icon: Objects/Materials/ingots.rsi/gold.png
color: "#FFD700" color: "#FFD700"
price: 0.0625
- type: material - type: material
id: Silver id: Silver
@@ -17,6 +19,7 @@
name: silver name: silver
icon: Objects/Materials/ingots.rsi/silver.png icon: Objects/Materials/ingots.rsi/silver.png
color: "#C0C0C0" color: "#C0C0C0"
price: 0.025
- type: material - type: material
id: Plasteel id: Plasteel
@@ -24,3 +27,4 @@
name: plasteel name: plasteel
icon: Objects/Materials/Sheets/metal.rsi/plasteel.png icon: Objects/Materials/Sheets/metal.rsi/plasteel.png
color: "#696969" #Okay, this is epic color: "#696969" #Okay, this is epic
price: 0.1025 # 1-1 mix of plasma and steel.

View File

@@ -27,6 +27,7 @@
shoes: ClothingShoesBootsWork shoes: ClothingShoesBootsWork
id: PassengerPDA id: PassengerPDA
belt: ClothingBeltUtility belt: ClothingBeltUtility
pocket1: AppraisalTool
innerclothingskirt: ClothingUniformJumpskirtColorLightBrown innerclothingskirt: ClothingUniformJumpskirtColorLightBrown
satchel: ClothingBackpackSatchelEngineering satchel: ClothingBackpackSatchelEngineering
duffelbag: ClothingBackpackSatchelEngineering duffelbag: ClothingBackpackSatchelEngineering

View File

@@ -79,3 +79,13 @@
showInVote: false showInVote: false
rules: rules:
- Nukeops - Nukeops
- type: gamePreset
id: Pirates
alias:
- pirates
name: pirates-title
description: pirates-description
showInVote: false
rules:
- Pirates

View File

@@ -10,6 +10,9 @@
- type: gameRule - type: gameRule
id: Nukeops id: Nukeops
- type: gameRule
id: Pirates
- type: gameRule - type: gameRule
id: Suspicion id: Suspicion

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@@ -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
}