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.UserInterface;
using Content.Shared.Cargo;

View File

@@ -1,3 +1,4 @@
using Content.Server.Cargo.Systems;
using Content.Shared.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.Components;
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.GameTicking;
namespace Content.Server.Cargo
namespace Content.Server.Cargo.Systems
{
public sealed partial class CargoSystem
{

View File

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

View File

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

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 =
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
*/

View File

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

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
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitRd
- type: StaticPrice
price: 750
- type: entity
parent: ClothingOuterHardsuitBase

View File

@@ -46,6 +46,8 @@
sprintModifier: 1
enabled: false
- type: NoSlip
- type: StaticPrice
price: 750
- type: entity
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
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,8 @@
max: 999999 # todo: add support for unlimited stacks
stackType: Telecrystal
- type: Telecrystal
- type: StackPrice
price: 200
- type: entity
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
- type: CablePlacer
- type: Clickable
- type: StackPrice
price: 0.5
- type: entity
id: CableHVStack

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -79,3 +79,13 @@
showInVote: false
rules:
- 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
id: Nukeops
- type: gameRule
id: Pirates
- type: gameRule
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
}