expedition rewards (#16972)

Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
deltanedas
2023-06-16 05:19:02 +00:00
committed by GitHub
parent f7e4a69b65
commit b9f24b2681
19 changed files with 845 additions and 1582 deletions

View File

@@ -129,13 +129,13 @@ namespace Content.Client.Cargo.UI
foreach (var order in orders)
{
var product = _protoManager.Index<CargoProductPrototype>(order.ProductId);
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
var row = new CargoOrderRow
{
Order = order,
Icon = { Texture = _spriteSystem.Frame0(product.Icon) },
Icon = { Texture = _spriteSystem.Frame0(product) },
ProductName =
{
Text = Loc.GetString(

View File

@@ -39,13 +39,13 @@ namespace Content.Client.Cargo.UI
foreach (var order in orders)
{
var product = _protoManager.Index<CargoProductPrototype>(order.ProductId);
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
var row = new CargoOrderRow
{
Order = order,
Icon = { Texture = _spriteSystem.Frame0(product.Icon) },
Icon = { Texture = _spriteSystem.Frame0(product) },
ProductName =
{
Text = Loc.GetString(

View File

@@ -18,6 +18,8 @@ using Content.Shared.Access.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Cargo.Systems
{
@@ -33,14 +35,6 @@ namespace Content.Server.Cargo.Systems
/// </summary>
private float _timer;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly DeviceLinkSystem _linker = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
private void InitializeConsole()
{
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
@@ -72,6 +66,8 @@ namespace Content.Server.Cargo.Systems
{
_timer += frameTime;
// TODO: Doesn't work with serialization and shouldn't just be updating every delay
// client can just interp this just fine on its own.
while (_timer > Delay)
{
_timer -= Delay;
@@ -124,7 +120,7 @@ namespace Content.Server.Cargo.Systems
}
// Invalid order
if (!_protoMan.TryIndex<CargoProductPrototype>(order.ProductId, out var product))
if (!_protoMan.HasIndex<EntityPrototype>(order.ProductId))
{
ConsolePopup(args.Session, Loc.GetString("cargo-console-invalid-product"));
PlayDenySound(uid, component);
@@ -152,7 +148,7 @@ namespace Content.Server.Cargo.Systems
PlayDenySound(uid, component);
}
var cost = product.PointCost * order.OrderQuantity;
var cost = order.Price * order.OrderQuantity;
// Not enough balance
if (cost > bankAccount.Balance)
@@ -163,7 +159,7 @@ namespace Content.Server.Cargo.Systems
}
_idCardSystem.TryFindIdCard(player, out var idCard);
order.SetApproverData(idCard);
order.SetApproverData(idCard?.FullName, idCard?.JobTitle);
_audio.PlayPvs(_audio.GetSound(component.ConfirmSound), uid);
// Log order approval
@@ -190,11 +186,20 @@ namespace Content.Server.Cargo.Systems
return;
var bank = GetBankAccount(component);
if (bank == null) return;
var orderDatabase = GetOrderDatabase(component);
if (orderDatabase == null) return;
if (bank == null)
return;
var data = GetOrderData(args, GenerateOrderId(orderDatabase));
var orderDatabase = GetOrderDatabase(component);
if (orderDatabase == null)
return;
if (!_protoMan.TryIndex<CargoProductPrototype>(args.CargoProductId, out var product))
{
_sawmill.Error($"Tried to add invalid cargo product {args.CargoProductId} as order!");
return;
}
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase));
if (!TryAddOrder(orderDatabase, data))
{
@@ -239,9 +244,9 @@ namespace Content.Server.Cargo.Systems
_audio.PlayPvs(_audio.GetSound(component.ErrorSound), uid);
}
private CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, int id)
private CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, CargoProductPrototype cargoProduct, int id)
{
return new CargoOrderData(id, args.ProductId, args.Amount, args.Requester, args.Reason);
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.PointCost, args.Amount, args.Requester, args.Reason);
}
public int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component)
@@ -286,21 +291,15 @@ namespace Content.Server.Cargo.Systems
}
}
public bool AddAndApproveOrder(StationCargoOrderDatabaseComponent component, string productId, int qty, string sender, string description, string dest)
public bool AddAndApproveOrder(StationCargoOrderDatabaseComponent component, string spawnId, int cost, int qty, string sender, string description, string dest)
{
if (!_prototypeManager.HasIndex<CargoProductPrototype>(productId))
{
_sawmill.Warning($"CargoSystem.Orders could not find CargoProductPrototype for '{productId}' in {description}.");
// Pretend that it worked OK, since we don't want the caller to try again.
return true;
}
DebugTools.Assert(_protoMan.HasIndex<EntityPrototype>(spawnId));
// Make an order
var id = GenerateOrderId(component);
var order = new CargoOrderData(id, productId, qty, sender, description);
var order = new CargoOrderData(id, spawnId, cost, qty, sender, description);
// Approve it now
order.SetApproverData(new IdCardComponent(){FullName = dest, JobTitle = sender});
order.SetApproverData(dest, sender);
// Log order addition
_adminLogger.Add(LogType.Action, LogImpact.Low,
@@ -368,7 +367,7 @@ namespace Content.Server.Cargo.Systems
if (PopFrontOrder(orderDB, out var order))
{
// Create the item itself
var item = Spawn(_protoMan.Index<CargoProductPrototype>(order.ProductId).Product, whereToPutIt);
var item = Spawn(order.ProductId, whereToPutIt);
// Create a sheet of paper to write the order details on
var printed = EntityManager.SpawnEntity(paperPrototypeToPrint, whereToPutIt);

View File

@@ -29,15 +29,6 @@ public sealed partial class CargoSystem
* Handles cargo shuttle mechanics.
*/
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StackSystem _stack = default!;
public MapId? CargoMap { get; private set; }
private void InitializeShuttle()
@@ -191,7 +182,7 @@ public sealed partial class CargoSystem
// We won't be able to fit the whole order on, so make one
// which represents the space we do have left:
var reducedOrder = new CargoOrderData(order.OrderId,
order.ProductId, spaceRemaining, order.Requester, order.Reason);
order.ProductId, order.Price, spaceRemaining, order.Requester, order.Reason);
orders.Add(reducedOrder);
}
else
@@ -335,7 +326,7 @@ public sealed partial class CargoSystem
}
SellPallets(gridUid, out var price);
var stackPrototype = _prototypeManager.Index<StackPrototype>(component.CashType);
var stackPrototype = _protoMan.Index<StackPrototype>(component.CashType);
_stack.Spawn((int)price, stackPrototype, uid.ToCoordinates());
UpdatePalletConsoleInterface(uid);
}

View File

@@ -12,9 +12,6 @@ namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem
{
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private void InitializeTelepad()
{
SubscribeLocalEvent<CargoTelepadComponent, ComponentInit>(OnInit);

View File

@@ -1,17 +1,46 @@
using Content.Server.Access.Systems;
using Content.Server.Cargo.Components;
using Content.Server.DeviceLinking.Systems;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Server.Shuttles.Systems;
using Content.Server.Stack;
using Content.Server.Station.Systems;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Logs;
using Content.Shared.Cargo;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem : SharedCargoSystem
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly DeviceLinkSystem _linker = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly ItemSlotsSystem _slots = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
private ISawmill _sawmill = default!;

View File

@@ -1,6 +1,8 @@
using Content.Shared.Random;
using Content.Shared.Salvage;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Salvage.Expeditions;
@@ -27,7 +29,7 @@ public sealed class SalvageExpeditionComponent : Component
/// <summary>
/// Station whose mission this is.
/// </summary>
[ViewVariables, DataField("station")]
[DataField("station")]
public EntityUid Station;
[ViewVariables] public bool Completed = false;
@@ -48,6 +50,12 @@ public sealed class SalvageExpeditionComponent : Component
{
Params = AudioParams.Default.WithVolume(-15),
};
/// <summary>
/// The difficulty this mission had or, in the future, was selected.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("difficulty")]
public DifficultyRating Difficulty;
}
public enum ExpeditionStage : byte

View File

@@ -1,13 +1,17 @@
using System.Linq;
using System.Threading;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues;
using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.Salvage.Expeditions;
using Content.Server.Salvage.Expeditions.Structure;
using Content.Server.Station.Systems;
using Content.Shared.CCVar;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Examine;
using Content.Shared.Salvage;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues;
using System.Linq;
using System.Threading;
namespace Content.Server.Salvage;
@@ -17,6 +21,8 @@ public sealed partial class SalvageSystem
* Handles setup / teardown of salvage expeditions.
*/
[Dependency] private readonly CargoSystem _cargo = default!;
private const int MissionLimit = 5;
private readonly JobQueue _salvageQueue = new();
@@ -99,7 +105,7 @@ public sealed partial class SalvageSystem
// Finish mission
if (TryComp<SalvageExpeditionDataComponent>(component.Station, out var data))
{
FinishExpedition(data, component, null);
FinishExpedition(data, uid, component, null);
}
}
@@ -141,7 +147,7 @@ public sealed partial class SalvageSystem
}
}
private void FinishExpedition(SalvageExpeditionDataComponent component, SalvageExpeditionComponent expedition, EntityUid? shuttle)
private void FinishExpedition(SalvageExpeditionDataComponent component, EntityUid uid, SalvageExpeditionComponent expedition, EntityUid? shuttle)
{
// Finish mission cleanup.
switch (expedition.MissionParams.MissionType)
@@ -150,7 +156,7 @@ public sealed partial class SalvageSystem
case SalvageMissionType.Mining:
expedition.Completed = true;
if (shuttle != null && TryComp<SalvageMiningExpeditionComponent>(expedition.Owner, out var mining))
if (shuttle != null && TryComp<SalvageMiningExpeditionComponent>(uid, out var mining))
{
var xformQuery = GetEntityQuery<TransformComponent>();
var entities = new List<EntityUid>();
@@ -169,18 +175,19 @@ public sealed partial class SalvageSystem
break;
}
// Payout already handled elsewhere.
// Handle payout after expedition has finished
if (expedition.Completed)
{
_sawmill.Debug($"Completed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown);
Announce(expedition.Owner, Loc.GetString("salvage-expedition-mission-completed"));
Announce(uid, Loc.GetString("salvage-expedition-mission-completed"));
GiveRewards(expedition);
}
else
{
_sawmill.Debug($"Failed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_failedCooldown);
Announce(expedition.Owner, Loc.GetString("salvage-expedition-mission-failed"));
Announce(uid, Loc.GetString("salvage-expedition-mission-failed"));
}
component.ActiveMission = 0;
@@ -270,4 +277,51 @@ public sealed partial class SalvageSystem
{
args.PushMarkup(Loc.GetString("salvage-expedition-structure-examine"));
}
private void GiveRewards(SalvageExpeditionComponent comp)
{
// send it to cargo, no rewards otherwise.
if (!TryComp<StationCargoOrderDatabaseComponent>(comp.Station, out var cargoDb))
{
return;
}
var ids = RewardsForDifficulty(comp.Difficulty);
foreach (var id in ids)
{
// pick a random reward to give
var rewards = _prototypeManager.Index<WeightedRandomPrototype>(id);
var reward = rewards.Pick(_random);
var sender = Loc.GetString("cargo-gift-default-sender");
var desc = Loc.GetString("salvage-expedition-reward-description");
var dest = Loc.GetString("cargo-gift-default-dest");
_cargo.AddAndApproveOrder(cargoDb, reward, 0, 1, sender, desc, dest);
}
}
/// <summary>
/// Get a list of WeightedRandomPrototype IDs with the rewards for a certain difficulty.
/// </summary>
private string[] RewardsForDifficulty(DifficultyRating rating)
{
var common = "SalvageRewardCommon";
var rare = "SalvageRewardRare";
var epic = "SalvageRewardEpic";
switch (rating)
{
case DifficultyRating.Minimal:
return new string[] { common, common, common };
case DifficultyRating.Minor:
return new string[] { common, common, rare };
case DifficultyRating.Moderate:
return new string[] { common, rare, rare };
case DifficultyRating.Hazardous:
return new string[] { rare, rare, epic };
case DifficultyRating.Extreme:
return new string[] { rare, epic, epic };
default:
throw new NotImplementedException();
}
}
}

View File

@@ -163,8 +163,7 @@ public sealed partial class SalvageSystem
else if (comp.Stage < ExpeditionStage.MusicCountdown && remaining < TimeSpan.FromMinutes(2))
{
// TODO: Some way to play audio attached to a map for players.
comp.Stream = _audio.PlayGlobal(comp.Sound,
Filter.BroadcastMap(Comp<MapComponent>(uid).MapId), true);
comp.Stream = _audio.PlayGlobal(comp.Sound, Filter.BroadcastMap(Comp<MapComponent>(uid).MapId), true);
comp.Stage = ExpeditionStage.MusicCountdown;
Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(2).Minutes)));
}
@@ -209,7 +208,7 @@ public sealed partial class SalvageSystem
}
}
// Mining missions: NOOP
// Mining missions: NOOP since it's handled after ftling
// Structure missions
var structureQuery = EntityQueryEnumerator<SalvageStructureExpeditionComponent, SalvageExpeditionComponent>();

View File

@@ -133,6 +133,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
expedition.Station = Station;
expedition.EndTime = _timing.CurTime + mission.Duration;
expedition.MissionParams = _missionParams;
expedition.Difficulty = _missionParams.Difficulty;
// Don't want consoles to have the incorrect name until refreshed.
var ftlUid = _entManager.CreateEntityUninitialized("FTLPoint", new EntityCoordinates(mapUid, Vector2.Zero));

View File

@@ -68,7 +68,16 @@ public sealed class CargoGiftsRule : StationEventSystem<CargoGiftsRuleComponent>
var (productId, qty) = component.Gifts.First();
component.Gifts.Remove(productId);
if (!_cargoSystem.AddAndApproveOrder(cargoDb, productId, qty, Loc.GetString(component.Sender), Loc.GetString(component.Description), Loc.GetString(component.Dest)))
var product = _prototypeManager.Index<CargoProductPrototype>(productId);
if (!_cargoSystem.AddAndApproveOrder(
cargoDb,
product.Product,
product.PointCost,
qty,
Loc.GetString(component.Sender),
Loc.GetString(component.Description),
Loc.GetString(component.Dest)))
{
break;
}

View File

@@ -6,13 +6,18 @@ namespace Content.Shared.Cargo
[NetSerializable, Serializable]
public sealed class CargoOrderData
{
/// <summary>
/// Price when the order was added.
/// </summary>
public int Price;
/// <summary>
/// A unique (arbitrary) ID which identifies this order.
/// </summary>
public readonly int OrderId;
/// <summary>
/// Prototype id for the item to create
/// Prototype Id for the item to be created
/// </summary>
public readonly string ProductId;
@@ -24,7 +29,7 @@ namespace Content.Shared.Cargo
/// <summary>
/// How many instances of this order that we've already dispatched
/// <summary>
/// </summary>
public int NumDispatched = 0;
public readonly string Requester;
@@ -34,25 +39,26 @@ namespace Content.Shared.Cargo
public bool Approved => Approver is not null;
public string? Approver;
public CargoOrderData(int orderId, string productId, int amount, string requester, string reason)
public CargoOrderData(int orderId, string productId, int price, int amount, string requester, string reason)
{
OrderId = orderId;
ProductId = productId;
Price = price;
OrderQuantity = amount;
Requester = requester;
Reason = reason;
}
public void SetApproverData(IdCardComponent? idCard)
public void SetApproverData(string? fullName, string? jobTitle)
{
var sb = new StringBuilder();
if (!string.IsNullOrWhiteSpace(idCard?.FullName))
if (!string.IsNullOrWhiteSpace(fullName))
{
sb.Append($"{idCard.FullName} ");
sb.Append($"{fullName} ");
}
if (!string.IsNullOrWhiteSpace(idCard?.JobTitle))
if (!string.IsNullOrWhiteSpace(jobTitle))
{
sb.Append($"({idCard.JobTitle})");
sb.Append($"({jobTitle})");
}
Approver = sb.ToString();
}

View File

@@ -10,14 +10,14 @@ public sealed class CargoConsoleAddOrderMessage : BoundUserInterfaceMessage
{
public string Requester;
public string Reason;
public string ProductId;
public string CargoProductId;
public int Amount;
public CargoConsoleAddOrderMessage(string requester, string reason, string productId, int amount)
public CargoConsoleAddOrderMessage(string requester, string reason, string cargoProductId, int amount)
{
Requester = requester;
Reason = reason;
ProductId = productId;
CargoProductId = cargoProductId;
Amount = amount;
}
}

View File

@@ -46,3 +46,4 @@ salvage-expedition-announcement-countdown-minutes = {$duration} minutes remainin
salvage-expedition-announcement-countdown-seconds = {$duration} seconds remaining to complete the expedition.
salvage-expedition-announcement-dungeon = Dungeon is located {$direction}.
salvage-expedition-completed = Expedition is completed.
salvage-expedition-reward-description = Mission completion reward

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -67,13 +67,12 @@
offset: 0.0
- type: entity
name: Salvage T3/4 Machine Parts Spawner
id: SalvagePartsT3Spawner
parent: MarkerBase
id: SalvagePartsT3T4Spawner
name: tier 3/4 machine part
components:
- type: Sprite
layers:
- state: red
- sprite: Objects/Misc/stock_parts.rsi
state: super_matter_bin
- type: RandomSpawner
@@ -89,6 +88,40 @@
chance: 0.95
offset: 0.0
- type: entity
parent: MarkerBase
id: SalvagePartsT3Spawner
name: tier 3 machine part
suffix: Spawner
components:
- type: Sprite
layers:
- sprite: Objects/Misc/stock_parts.rsi
state: super_matter_bin
- type: RandomSpawner
prototypes:
- SuperCapacitorStockPart
- PicoManipulatorStockPart
- SuperMatterBinStockPart
offset: 0.0
- type: entity
parent: MarkerBase
id: SalvagePartsT4Spawner
name: tier 4 machine part
suffix: Spawner
components:
- type: Sprite
layers:
- sprite: Objects/Misc/stock_parts.rsi
state: bluespace_matter_bin
- type: RandomSpawner
prototypes:
- QuadraticCapacitorStockPart
- PicoManipulatorStockPart
- BluespaceMatterBinStockPart
offset: 0.0
- type: entity
name: Salvage Mob Spawner
id: SalvageMobSpawner

View File

@@ -238,3 +238,19 @@
- type: Tag
tags:
- Bottle
- type: entity
parent: BaseChemistryEmptyBottle
id: CognizineChemistryBottle
name: cognizine bottle
components:
- type: SolutionContainerManager
solutions:
drink:
maxVol: 30
reagents:
- ReagentId: Cognizine
Quantity: 30
- type: Tag
tags:
- Bottle

View File

@@ -0,0 +1,61 @@
- type: weightedRandom
id: SalvageRewardCommon
weights:
# basic materials
CrateMaterialGlass: 1.0
CrateMaterialPlasteel: 1.0
CrateMaterialPlastic: 1.0
CrateMaterialSteel: 1.0
# things the station might want
CrateEngineeringAMEJar: 0.25
CrateFoodPizzaLarge: 0.25
CrateFoodSoftdrinks: 0.25
CrateFunATV: 0.25
CrateFunInstrumentsVariety: 0.25
CrateSalvageEquipment: 0.25
RandomArtifactSpawner: 0.25
# weighted down since it sells for a lot
NuclearBombKeg: 0.1
- type: weightedRandom
id: SalvageRewardRare
weights:
# rare materials
IngotGold: 1.0
IngotSilver: 1.0
SheetPlasma: 1.0
SheetUranium: 1.0
SalvagePartsT3Spawner: 1.0
SalvagePartsT3T4Spawner: 1.0
# things the expedition team might want
CrateEmergencyBruteKit: 0.5
CrateMedicalSupplies: 0.5
CrateSecurityRiot: 0.5
# cloning boards
CloningPodMachineCircuitboard: 0.5
MedicalScannerMachineCircuitboard: 0.5
CloningConsoleComputerCircuitboard: 0.5
# basic weapons
CrateArmorySMG: 0.25
CrateArmoryLaser: 0.25
WeaponMakeshiftLaser: 0.25
# rare weapons
WeaponSubMachineGunC20r: 0.1
- type: weightedRandom
id: SalvageRewardEpic
weights:
# rare machinery
SecurityTechFabCircuitboard: 1.0
ResearchAndDevelopmentServerMachineCircuitboard: 1.0
SalvagePartsT4Spawner: 1.0
# rare weapons
WeaponAdvancedLaser: 1.0
WeaponLaserCannon: 1.0
WeaponXrayCannon: 1.0
WeaponSniperHristov: 1.0
# extremely rare weapons
WeaponLauncherRocket: 0.1
# rare chemicals
CognizineChemistryBottle: 1.0
OmnizineChemistryBottle: 1.0