365 lines
15 KiB
C#
365 lines
15 KiB
C#
using Barotrauma.Items.Components;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class CargoMission : Mission
|
|
{
|
|
private readonly ContentXElement itemConfig;
|
|
|
|
private readonly List<Item> items = new List<Item>();
|
|
private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
|
|
private readonly Dictionary<Item, int> inventorySlotIndices = new Dictionary<Item, int>();
|
|
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
|
|
|
|
/// <summary>
|
|
/// Percentage of items (0.0 - 1.0) needed to be delivered to complete the mission.
|
|
/// </summary>
|
|
private float requiredDeliveryAmount;
|
|
|
|
private readonly List<(ContentXElement element, ItemContainer container)> itemsToSpawn = new List<(ContentXElement element, ItemContainer container)>();
|
|
private int? rewardPerCrate;
|
|
private int calculatedReward;
|
|
private int maxItemCount;
|
|
|
|
private Submarine currentSub;
|
|
private SubmarineInfo nextRoundSubInfo;
|
|
|
|
private readonly List<CargoMission> previouslySelectedMissions = new List<CargoMission>();
|
|
|
|
public override LocalizedString Description
|
|
{
|
|
get
|
|
{
|
|
if ((GameMain.GameSession?.Campaign?.PendingSubmarineSwitch ?? Submarine.MainSub?.Info) != nextRoundSubInfo)
|
|
{
|
|
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(Submarine.MainSub))}‖end‖";
|
|
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
|
}
|
|
return description;
|
|
}
|
|
}
|
|
|
|
public CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
|
: base(prefab, locations, sub)
|
|
{
|
|
this.currentSub = sub;
|
|
this.nextRoundSubInfo = sub?.Info;
|
|
itemConfig = prefab.ConfigElement.GetChildElement("Items");
|
|
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
|
|
//this can get called between rounds when the client receives a campaign save
|
|
//don't attempt to determine cargo if the sub hasn't been fully loaded
|
|
if (sub == null || sub.Loading || sub.Removed || Submarine.Unloading || !Submarine.Loaded.Contains(sub))
|
|
{
|
|
return;
|
|
}
|
|
DetermineCargo();
|
|
}
|
|
|
|
private void DetermineCargo()
|
|
{
|
|
if (this.currentSub == null || itemConfig == null)
|
|
{
|
|
calculatedReward = Prefab.Reward;
|
|
return;
|
|
}
|
|
|
|
itemsToSpawn.Clear();
|
|
|
|
maxItemCount = 0;
|
|
foreach (var subElement in itemConfig.Elements())
|
|
{
|
|
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
|
maxItemCount += maxCount;
|
|
}
|
|
|
|
var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
|
|
if (pendingSubInfo != null && pendingSubInfo != currentSub.Info)
|
|
{
|
|
//if we've got a submarine switch pending, calculate the amount of cargo based on it's cargo capacity
|
|
//TODO: this isn't guaranteed to be accurate, because we don't take existing items in the new sub's cargo containers
|
|
//or items that might get transferred in them into account
|
|
maxItemCount = Math.Min(maxItemCount, pendingSubInfo.CargoCapacity);
|
|
previouslySelectedMissions.Clear();
|
|
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
|
{
|
|
bool isPriorMission = true;
|
|
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
|
|
{
|
|
if (mission is not CargoMission otherMission) { continue; }
|
|
if (mission == this) { isPriorMission = false; }
|
|
previouslySelectedMissions.Add(otherMission);
|
|
if (!isPriorMission) { continue; }
|
|
maxItemCount -= otherMission.itemsToSpawn.Count;
|
|
}
|
|
}
|
|
for (int i = 0; i < maxItemCount; i++)
|
|
{
|
|
foreach (var subElement in itemConfig.Elements())
|
|
{
|
|
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
|
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
|
// For logging purposes
|
|
FindItemPrefab(subElement);
|
|
while (itemsToSpawn.Count < maxItemCount)
|
|
{
|
|
itemsToSpawn.Add((subElement, null));
|
|
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
|
}
|
|
}
|
|
}
|
|
maxItemCount = Math.Max(0, maxItemCount);
|
|
nextRoundSubInfo = pendingSubInfo;
|
|
}
|
|
else
|
|
{
|
|
List<(ItemContainer container, int freeSlots)> containers = currentSub.GetCargoContainers();
|
|
containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
|
|
|
|
previouslySelectedMissions.Clear();
|
|
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
|
{
|
|
bool isPriorMission = true;
|
|
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
|
|
{
|
|
if (mission is not CargoMission otherMission) { continue; }
|
|
if (mission == this) { isPriorMission = false; }
|
|
previouslySelectedMissions.Add(otherMission);
|
|
if (!isPriorMission) { continue; }
|
|
foreach (var (element, container) in otherMission.itemsToSpawn)
|
|
{
|
|
for (int i = 0; i < containers.Count; i++)
|
|
{
|
|
if (containers[i].container == container)
|
|
{
|
|
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < containers.Count; i++)
|
|
{
|
|
foreach (var subElement in itemConfig.Elements())
|
|
{
|
|
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
|
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
|
ItemPrefab itemPrefab = FindItemPrefab(subElement);
|
|
while (containers[i].freeSlots > 0 && containers[i].container.Inventory.CanBePut(itemPrefab))
|
|
{
|
|
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
|
itemsToSpawn.Add((subElement, containers[i].container));
|
|
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!itemsToSpawn.Any())
|
|
{
|
|
itemsToSpawn.Add((itemConfig.Elements().First(), null));
|
|
}
|
|
|
|
// Calculate the current total reward, since it might differ from the
|
|
// prefab total reward depending on the current actual crate count.
|
|
calculatedReward = 0;
|
|
bool crateValuesUniform = true;
|
|
int? prevCrateReward = null;
|
|
foreach (var (element, container) in itemsToSpawn)
|
|
{
|
|
int currentCrateReward = element.GetAttributeInt("reward", 0);
|
|
calculatedReward += currentCrateReward;
|
|
|
|
// Apparently crates can have varying values, so we need to check
|
|
// here if that is the case, stopping checks on the first discrepancy
|
|
if (crateValuesUniform)
|
|
{
|
|
if (prevCrateReward.HasValue)
|
|
{
|
|
if (prevCrateReward.Value != currentCrateReward)
|
|
{
|
|
crateValuesUniform = false;
|
|
}
|
|
}
|
|
prevCrateReward = currentCrateReward;
|
|
}
|
|
}
|
|
|
|
if (crateValuesUniform)
|
|
{
|
|
// If rewardPerCrate is set, it will be displayed in the client UI as eg. "123 mk x 5"
|
|
rewardPerCrate = calculatedReward / itemsToSpawn.Count;
|
|
}
|
|
else
|
|
{
|
|
// If rewardPerCrate is null, the client UI will display just the total reward
|
|
rewardPerCrate = null;
|
|
}
|
|
|
|
// Apply the mission reward campaign setting multiplier to the per-crate price, too
|
|
if (GameMain.GameSession?.Campaign is CampaignMode campaign && rewardPerCrate is int confirmedRewardPerCrate)
|
|
{
|
|
rewardPerCrate = (int)Math.Round(confirmedRewardPerCrate * campaign.Settings.MissionRewardMultiplier);
|
|
}
|
|
|
|
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(currentSub))}‖end‖";
|
|
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
|
}
|
|
|
|
public override int GetBaseReward(Submarine sub)
|
|
{
|
|
// If we are not at the location of the mission, skip the calculation of the reward
|
|
if (GameMain.GameSession?.StartLocation != Locations[0])
|
|
{
|
|
return calculatedReward;
|
|
}
|
|
|
|
bool missionsChanged = false;
|
|
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
|
{
|
|
List<Mission> currentMissions = GameMain.GameSession.StartLocation.SelectedMissions.Where(m => m is CargoMission).ToList();
|
|
if (currentMissions.Count != previouslySelectedMissions.Count)
|
|
{
|
|
missionsChanged = true;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < previouslySelectedMissions.Count; i++)
|
|
{
|
|
if (previouslySelectedMissions[i] != currentMissions[i])
|
|
{
|
|
missionsChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
|
|
if (pendingSubInfo != null && nextRoundSubInfo != pendingSubInfo)
|
|
{
|
|
this.nextRoundSubInfo = pendingSubInfo;
|
|
DetermineCargo();
|
|
}
|
|
else if (sub != this.currentSub || missionsChanged)
|
|
{
|
|
this.currentSub = sub;
|
|
this.nextRoundSubInfo = sub?.Info;
|
|
DetermineCargo();
|
|
}
|
|
|
|
return calculatedReward;
|
|
}
|
|
|
|
private void InitItems()
|
|
{
|
|
this.currentSub = Submarine.MainSub;
|
|
DetermineCargo();
|
|
|
|
items.Clear();
|
|
parentInventoryIDs.Clear();
|
|
parentItemContainerIndices.Clear();
|
|
inventorySlotIndices.Clear();
|
|
|
|
if (itemConfig == null)
|
|
{
|
|
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)",
|
|
contentPackage: Prefab.ContentPackage);
|
|
return;
|
|
}
|
|
|
|
foreach (var (element, container) in itemsToSpawn)
|
|
{
|
|
LoadItemAsChild(element, container?.Item);
|
|
}
|
|
|
|
if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; }
|
|
}
|
|
|
|
private void LoadItemAsChild(ContentXElement element, Item parent)
|
|
{
|
|
ItemPrefab itemPrefab = FindItemPrefab(element);
|
|
|
|
Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub);
|
|
if (!position.HasValue) { return; }
|
|
|
|
var item = new Item(itemPrefab, position.Value, cargoRoomSub)
|
|
{
|
|
SpawnedInCurrentOutpost = true,
|
|
AllowStealing = false
|
|
};
|
|
item.AddTag(Tags.CargoMissionItem);
|
|
item.AddTag(Prefab.Identifier);
|
|
foreach (var tag in Prefab.Tags)
|
|
{
|
|
item.AddTag(tag);
|
|
}
|
|
item.FindHull();
|
|
items.Add(item);
|
|
|
|
if (parent?.GetComponent<ItemContainer>() != null)
|
|
{
|
|
parentInventoryIDs.Add(item, parent.ID);
|
|
parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent<ItemContainer>()));
|
|
parent.Combine(item, user: null);
|
|
inventorySlotIndices.Add(item, item.ParentInventory?.FindIndex(item) ?? -1);
|
|
}
|
|
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
int amount = subElement.GetAttributeInt("amount", 1);
|
|
for (int i = 0; i < amount; i++)
|
|
{
|
|
LoadItemAsChild(subElement, item);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void StartMissionSpecific(Level level)
|
|
{
|
|
items.Clear();
|
|
parentInventoryIDs.Clear();
|
|
|
|
if (!IsClient)
|
|
{
|
|
InitItems();
|
|
}
|
|
}
|
|
|
|
protected override bool DetermineCompleted()
|
|
{
|
|
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
|
{
|
|
int deliveredItemCount = items.Count(it => IsItemDelivered(it));
|
|
if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected override void EndMissionSpecific(bool completed)
|
|
{
|
|
foreach (Item item in items)
|
|
{
|
|
if (!item.Removed) { item.Remove(); }
|
|
}
|
|
items.Clear();
|
|
failed = !completed;
|
|
}
|
|
|
|
private static bool IsItemDelivered(Item item)
|
|
{
|
|
if (item.Removed || item.Condition <= 0.0f || Submarine.MainSub == null) { return false; }
|
|
var submarine = item.Submarine ?? item.RootContainer?.Submarine;
|
|
return submarine == Submarine.MainSub || Submarine.MainSub.GetConnectedSubs().Contains(submarine);
|
|
}
|
|
}
|
|
}
|