Unstable v0.1300.0.0 (February 19th 2021)

This commit is contained in:
Joonas Rikkonen
2021-02-25 13:44:23 +02:00
parent b772654326
commit 24cbef485a
441 changed files with 21343 additions and 8562 deletions
@@ -33,8 +33,9 @@ namespace Barotrauma
{
OriginalContainerID = item.OriginalContainerID;
}
OriginalID = item.ID;
ModuleIndex = (ushort)item.OriginalModuleIndex;
ModuleIndex = (ushort) item.OriginalModuleIndex;
Identifier = item.prefab.Identifier;
}
@@ -42,6 +43,7 @@ namespace Barotrauma
{
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
}
public bool Matches(Item item)
{
if (item.OriginalContainerID != Entity.NullEntityID)
@@ -56,13 +58,17 @@ namespace Barotrauma
}
public readonly List<LocationConnection> Connections = new List<LocationConnection>();
private string baseName;
private int nameFormatIndex;
public bool Discovered;
public int TypeChangeTimer;
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
public int LocationTypeChangeCooldown;
public readonly int ZoneIndex;
public string BaseName { get => baseName; }
@@ -80,14 +86,76 @@ namespace Barotrauma
public Reputation Reputation { get; set; }
public int[] ProximityTime { get; private set; }
public int TurnsInRadiation { get; set; }
#region Store
private const float StoreMaxReputationModifier = 0.1f;
private const float StoreSellPriceModifier = 0.8f;
private const float MechanicalMaxDiscountPercentage = 50.0f;
private const float DailySpecialPriceModifier = 0.9f;
private const float RequestGoodPriceModifier = 1.5f;
public const int StoreInitialBalance = 5000;
public int StoreCurrentBalance { get; set; }
/// <summary>
/// In percentages
/// </summary>
private const int StorePriceModifierRange = 5;
/// <summary>
/// In percentages. Larger values make buying more expensive and selling less profitable, and vice versa.
/// </summary>
public int StorePriceModifier { get; private set; }
public Color BalanceColor => ActiveStoreBalanceStatus.Color;
public StoreBalanceStatus ActiveStoreBalanceStatus { get; private set; }
private static StoreBalanceStatus DefaultBalanceStatus { get; } = new StoreBalanceStatus(1.0f, 1.0f, Color.White);
private static List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>
{
new StoreBalanceStatus(0.5f, 0.75f, Color.Orange),
new StoreBalanceStatus(0.25f, 0.2f, Color.Red),
};
public struct StoreBalanceStatus
{
public float PercentageOfInitialBalance { get; }
public float SellPriceModifier { get; }
public Color Color { get; }
public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color)
{
PercentageOfInitialBalance = percentage;
SellPriceModifier = sellPriceModifier;
Color = color;
}
}
private int storeCurrentBalance;
public int StoreCurrentBalance
{
get
{
return storeCurrentBalance;
}
set
{
storeCurrentBalance = value;
ActiveStoreBalanceStatus = GetStoreBalanceStatus(value);
}
}
public List<PurchasedItem> StoreStock { get; set; }
public List<ItemPrefab> DailySpecials { get; } = new List<ItemPrefab>();
public List<ItemPrefab> RequestedGoods { get; } = new List<ItemPrefab>();
/// <summary>
/// How many map progress steps it takes before the discounts should be updated.
/// </summary>
private const int SpecialsUpdateInterval = 3;
private const int DailySpecialsCount = 3;
private const int RequestedGoodsCount = 3;
private int StepsSinceSpecialsUpdated { get; set; }
#endregion
private const float MechanicalMaxDiscountPercentage = 50.0f;
private readonly List<TakenItem> takenItems = new List<TakenItem>();
public IEnumerable<TakenItem> TakenItems
@@ -106,12 +174,11 @@ namespace Barotrauma
{
get
{
availableMissions.RemoveAll(m => m.Completed || m.Failed);
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
return availableMissions;
}
}
public Mission SelectedMission
{
get;
@@ -152,6 +219,10 @@ namespace Barotrauma
public string LastTypeChangeMessage;
public int TimeSinceLastTypeChange;
public bool IsGateBetweenBiomes;
private struct LoadedMission
{
public MissionPrefab MissionPrefab { get; }
@@ -175,29 +246,49 @@ namespace Barotrauma
return $"Location ({Name ?? "null"})";
}
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, IEnumerable<Location> existingLocations = null)
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
{
Type = LocationType.Random(rand, zone, requireOutpost);
Type = forceLocationType ?? LocationType.Random(rand, zone, requireOutpost);
Name = RandomName(Type, rand, existingLocations);
MapPosition = mapPosition;
PortraitId = ToolBox.StringToInt(Name);
Connections = new List<LocationConnection>();
ProximityTime = new int[Type.CanChangeTo.Count];
Connections = new List<LocationConnection>();
}
public Location(XElement element)
{
string locationType = element.GetAttributeString("type", "");
Type = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase));
bool typeNotFound = false;
if (Type == null)
{
DebugConsole.AddWarning($"Could not find location type \"{locationType}\". Using location type \"None\" instead.");
Type = LocationType.List.Find(lt => lt.Identifier.Equals("None", StringComparison.OrdinalIgnoreCase));
Type ??= LocationType.List.First();
typeNotFound = true;
}
baseName = element.GetAttributeString("basename", "");
Name = element.GetAttributeString("name", "");
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
TypeChangeTimer = element.GetAttributeInt("changetimer", 0);
Discovered = element.GetAttributeBool("discovered", false);
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
ProximityTime = element.GetAttributeIntArray("proximitytime", new int[Type.CanChangeTo.Count]);
if (ProximityTime.Length != Type.CanChangeTo.Count) { ProximityTime = new int[Type.CanChangeTo.Count]; }
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
if (!typeNotFound)
{
for (int i = 0; i < Type.CanChangeTo.Count; i++)
{
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
{
ProximityTimer.Add(Type.CanChangeTo[i].Requirements[j], element.GetAttributeInt("proximitytimer" + i + "-" + j, 0));
}
}
LoadLocationTypeChange(element);
}
string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]);
foreach (string takenItem in takenItemStr)
@@ -237,16 +328,42 @@ namespace Barotrauma
LevelData = new LevelData(element.Element("Level"));
PortraitId = ToolBox.StringToInt(Name);
if (element.GetChildElement("store") is XElement storeElement)
{
StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance);
StoreStock = LoadStoreStock(storeElement);
}
LoadStore(element);
LoadMissions(element);
}
public void LoadLocationTypeChange(XElement locationElement)
{
TimeSinceLastTypeChange = locationElement.GetAttributeInt("timesincelasttypechange", 0);
LocationTypeChangeCooldown = locationElement.GetAttributeInt("locationtypechangecooldown", 0);
foreach (XElement subElement in locationElement.Elements())
{
switch (subElement.Name.ToString())
{
case "pendinglocationtypechange":
int timer = subElement.GetAttributeInt("timer", 0);
if (subElement.Attribute("index") != null)
{
int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0);
PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null);
}
else
{
string missionIdentifier = subElement.GetAttributeString("missionidentifier", "");
var mission = MissionPrefab.List.Find(mp => mp.Identifier.Equals(missionIdentifier, StringComparison.OrdinalIgnoreCase));
if (mission == null)
{
DebugConsole.AddWarning($"Failed to activate a location type change from the mission \"{missionIdentifier}\" in location \"{Name}\". Matching mission not found.");
continue;
}
PendingLocationTypeChange = (mission.LocationTypeChangeOnCompleted, timer, mission);
}
break;
}
}
}
public void LoadMissions(XElement locationElement)
{
if (locationElement.GetChildElement("missions") is XElement missionsElement)
@@ -266,9 +383,9 @@ namespace Barotrauma
}
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, IEnumerable<Location> existingLocations = null)
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
{
return new Location(position, zone, rand, requireOutpost, existingLocations);
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
}
public void ChangeType(LocationType newType)
@@ -278,15 +395,34 @@ namespace Barotrauma
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
Type = newType;
ProximityTime = new int[Type.CanChangeTo.Count];
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
Name = Type.NameFormats == null ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
if (Type.MissionIdentifiers.Any())
{
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom());
}
if (Type.MissionTags.Any())
{
UnlockMissionByTag(Type.MissionTags.GetRandom());
}
CreateStore(force: true);
}
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
{
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
var mission = InstantiateMission(missionPrefab, ref connection);
var mission = InstantiateMission(missionPrefab, connection);
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
}
public void UnlockMission(MissionPrefab missionPrefab)
{
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
var mission = InstantiateMission(missionPrefab);
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
@@ -304,8 +440,7 @@ namespace Barotrauma
}
else
{
LocationConnection connection = null;
var mission = InstantiateMission(missionPrefab, ref connection);
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
//don't allow duplicate missions in the same connection
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
{
@@ -332,14 +467,13 @@ namespace Barotrauma
var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m));
if (unusedMissions.Any())
{
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this))));
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this)) || m.IsAllowed(this, this)));
if (!suitableMissions.Any())
{
suitableMissions = unusedMissions;
}
LocationConnection connection = null;
MissionPrefab missionPrefab = suitableMissions.GetRandom();
var mission = InstantiateMission(missionPrefab, ref connection);
MissionPrefab missionPrefab = ToolBox.SelectWeightedRandom(suitableMissions.ToList(), suitableMissions.Select(m => (float)m.Commonness).ToList(), Rand.RandSync.Unsynced);
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
//don't allow duplicate missions in the same connection
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
{
@@ -360,8 +494,14 @@ namespace Barotrauma
return null;
}
private Mission InstantiateMission(MissionPrefab prefab, ref LocationConnection connection)
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
{
if (prefab.IsAllowed(this, this))
{
connection = null;
return InstantiateMission(prefab);
}
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
if (!suitableConnections.Any())
{
@@ -371,21 +511,33 @@ namespace Barotrauma
connection = ToolBox.SelectWeightedRandom(
suitableConnections.ToList(),
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
Rand.RandSync.Unsynced);
Rand.RandSync.Unsynced);
return InstantiateMission(prefab, connection);
}
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
{
Location destination = connection.OtherLocation(this);
var mission = prefab.Instantiate(new Location[] { this, destination });
mission.AdjustLevelData(connection.LevelData);
return mission;
}
private Mission InstantiateMission(MissionPrefab prefab)
{
var mission = prefab.Instantiate(new Location[] { this, this });
mission.AdjustLevelData(LevelData);
return mission;
}
public void InstantiateLoadedMissions(Map map)
{
availableMissions.Clear();
if (loadedMissions == null || loadedMissions.None()) { return; }
foreach (LoadedMission loadedMission in loadedMissions)
{
Location destination = null;
Location destination;
if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count)
{
destination = map.Locations[loadedMission.DestinationIndex];
@@ -410,6 +562,23 @@ namespace Barotrauma
SelectedMissionIndex = -1;
}
public bool HasOutpost()
{
if (!Type.HasOutpost) { return false; }
return !IsCriticallyRadiated();
}
public bool IsCriticallyRadiated()
{
if (GameMain.GameSession is { Campaign: { Map: { } map } })
{
return TurnsInRadiation > map.Radiation.Params.CriticalRadiationThreshold;
}
return false;
}
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
{
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
@@ -456,6 +625,61 @@ namespace Barotrauma
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
}
public void LoadStore(XElement locationElement)
{
StoreStock?.Clear();
DailySpecials.Clear();
RequestedGoods.Clear();
if (locationElement.GetChildElement("store") is XElement storeElement)
{
StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance);
StorePriceModifier = storeElement.GetAttributeInt("pricemodifier", 0);
StoreStock ??= new List<PurchasedItem>();
foreach (XElement stockElement in storeElement.GetChildElements("stock"))
{
var id = stockElement.GetAttributeString("id", null);
if (string.IsNullOrWhiteSpace(id)) { continue; }
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
if (prefab == null) { continue; }
var qty = stockElement.GetAttributeInt("qty", 0);
if (qty < 1) { continue; }
StoreStock.Add(new PurchasedItem(prefab, qty));
}
StepsSinceSpecialsUpdated = storeElement.GetAttributeInt("stepssincespecialsupdated", 0);
if (storeElement.GetChildElement("dailyspecials") is XElement specialsElement)
{
var loadedDailySpecials = LoadStoreSpecials(specialsElement);
DailySpecials.AddRange(loadedDailySpecials);
}
if (storeElement.GetChildElement("requestedgoods") is XElement goodsElement)
{
var loadedRequestedGoods = LoadStoreSpecials(goodsElement);
RequestedGoods.AddRange(loadedRequestedGoods);
}
static List<ItemPrefab> LoadStoreSpecials(XElement element)
{
List<ItemPrefab> specials = new List<ItemPrefab>();
foreach (var childElement in element.GetChildElements("item"))
{
var id = childElement.GetAttributeString("id", null);
if (string.IsNullOrWhiteSpace(id)) { continue; }
var prefab = ItemPrefab.Find(null, id);
if (prefab == null) { continue; }
specials.Add(prefab);
}
return specials;
}
}
}
public bool IsRadiated() => GameMain.GameSession is { Campaign: { Map: { Radiation: { Enabled: true } radiation } } } && radiation.Contains(this);
private List<PurchasedItem> CreateStoreStock()
{
var stock = new List<PurchasedItem>();
@@ -463,31 +687,28 @@ namespace Barotrauma
{
if (prefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo))
{
var quantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount :
(priceInfo.MaxAvailableAmount > 0 ? Math.Min(priceInfo.MaxAvailableAmount, 5) : 5);
int quantity = PriceInfo.DefaultAmount;
if (priceInfo.MaxAvailableAmount > 0)
{
if (priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount)
{
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount);
}
else
{
quantity = priceInfo.MaxAvailableAmount;
}
}
else if (priceInfo.MinAvailableAmount > 0)
{
quantity = priceInfo.MinAvailableAmount;
}
stock.Add(new PurchasedItem(prefab, quantity));
}
}
return stock;
}
public static List<PurchasedItem> LoadStoreStock(XElement storeElement)
{
var stock = new List<PurchasedItem>();
if (storeElement == null) { return stock; }
foreach (XElement stockElement in storeElement.GetChildElements("stock"))
{
var id = stockElement.GetAttributeString("id", null);
if (string.IsNullOrWhiteSpace(id)) { continue; }
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
if (prefab == null) { continue; }
var qty = stockElement.GetAttributeInt("qty", 0);
if (qty < 1) { continue; }
stock.Add(new PurchasedItem(prefab, qty));
}
return stock;
}
/// <summary>
/// Mark the items that have been taken from the outpost to prevent them from spawning when re-entering the outpost
/// </summary>
@@ -526,48 +747,70 @@ namespace Barotrauma
}
}
public int GetAdjustedItemBuyPrice(PriceInfo priceInfo)
/// <param name="priceInfo">If null, item.GetPriceInfo() will be used to get it.</param>
/// /// <param name="considerDailySpecials">If false, the price won't be affected by <see cref="DailySpecialPriceModifier"/></param>
public int GetAdjustedItemBuyPrice(ItemPrefab item, PriceInfo priceInfo = null, bool considerDailySpecials = true)
{
// TODO: Check priceInfo.CanBeBought
priceInfo ??= item?.GetPriceInfo(this);
if (priceInfo == null) { return 0; }
var price = priceInfo.Price;
float price = priceInfo.Price;
// Adjust by random price modifier
price = ((100 + StorePriceModifier) / 100.0f) * price;
// Adjust by daily special status
if (considerDailySpecials && DailySpecials.Contains(item))
{
price = DailySpecialPriceModifier * price;
}
// Adjust by current location reputation
if (Reputation.Value > 0.0f)
{
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
price = MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price;
}
else
{
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
price = MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price;
}
// Item price should never go below 1 mk
return Math.Max(price, 1);
// Price should never go below 1 mk
return Math.Max((int)price, 1);
}
/// <summary>
/// If item.GetPriceInfo() returns null, this will return 0
/// </summary>
public int GetAdjustedItemBuyPrice(ItemPrefab item) => GetAdjustedItemBuyPrice(item?.GetPriceInfo(this));
public int GetAdjustedItemSellPrice(PriceInfo priceInfo)
/// <param name="priceInfo">If null, item.GetPriceInfo() will be used to get it.</param>
/// <param name="considerRequestedGoods">If false, the price won't be affected by <see cref="RequestGoodPriceModifier"/></param>
public int GetAdjustedItemSellPrice(ItemPrefab item, PriceInfo priceInfo = null, bool considerRequestedGoods = true)
{
priceInfo ??= item?.GetPriceInfo(this);
if (priceInfo == null) { return 0; }
var price = (int)(StoreSellPriceModifier * priceInfo.Price);
float price = StoreSellPriceModifier * priceInfo.Price;
// Adjust by random price modifier
price = ((100 - StorePriceModifier) / 100.0f) * price;
// Adjust by current store balance
price = ActiveStoreBalanceStatus.SellPriceModifier * price;
// Adjust by requested good status
if (considerRequestedGoods && RequestedGoods.Contains(item))
{
price = RequestGoodPriceModifier * price;
}
// Adjust by current location reputation
if (Reputation.Value > 0.0f)
{
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
price = MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price;
}
else
{
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
price = MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price;
}
// Item price should never go below 1 mk
return Math.Max(price, 1);
}
/// <summary>
/// If item.GetPriceInfo() returns null, this will return 0
/// </summary>
public int GetAdjustedItemSellPrice(ItemPrefab item) => GetAdjustedItemSellPrice(item?.GetPriceInfo(this));
// Price should never go below 1 mk
return Math.Max((int)price, 1);
}
public int GetAdjustedMechanicalCost(int cost)
{
@@ -575,12 +818,12 @@ namespace Barotrauma
return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
}
/// <summary>
/// If 'force' is true, the stock will be recreated even if it has been created previously already.
/// This is used when (at least) when the type of the location changes.
/// </summary>
/// <param name="force">If true, the store will be recreated if it already exists.</param>
public void CreateStore(bool force = false)
{
// In multiplayer, stores should be created by the server and loaded from save data by clients
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (!force && StoreStock != null) { return; }
if (StoreStock != null)
@@ -604,10 +847,16 @@ namespace Barotrauma
StoreCurrentBalance = StoreInitialBalance;
StoreStock = CreateStoreStock();
}
GenerateRandomPriceModifier();
CreateStoreSpecials();
}
public void UpdateStore()
{
// In multiplayer, stores should be updated by the server and loaded from save data by clients
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (StoreStock == null)
{
CreateStore();
@@ -619,6 +868,8 @@ namespace Barotrauma
StoreCurrentBalance = Math.Min(StoreCurrentBalance + (int)(StoreInitialBalance / 10.0f), StoreInitialBalance);
}
GenerateRandomPriceModifier();
var stock = StoreStock;
var stockToRemove = new List<PurchasedItem>();
foreach (PurchasedItem item in stock)
@@ -642,6 +893,56 @@ namespace Barotrauma
}
stockToRemove.ForEach(i => stock.Remove(i));
StoreStock = stock;
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval)
{
CreateStoreSpecials();
}
}
private void GenerateRandomPriceModifier()
{
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange);
}
private void CreateStoreSpecials()
{
DailySpecials.Clear();
var availableStock = new Dictionary<ItemPrefab, float>();
foreach (var stockItem in StoreStock)
{
if (stockItem.Quantity < 1) { continue; }
var weight = 1.0f;
var priceInfo = stockItem.ItemPrefab.GetPriceInfo(this);
if (priceInfo != null)
{
if (!priceInfo.CanBeSpecial) { continue; }
var baseQuantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount : PriceInfo.DefaultAmount;
weight += (float)(stockItem.Quantity - baseQuantity) / baseQuantity;
if (weight < 0.0f) { continue; }
}
availableStock.Add(stockItem.ItemPrefab, weight);
}
for (int i = 0; i < DailySpecialsCount; i++)
{
if (availableStock.None()) { break; }
var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced);
if (item == null) { break; }
DailySpecials.Add(item);
availableStock.Remove(item);
}
RequestedGoods.Clear();
for (int i = 0; i < RequestedGoodsCount; i++)
{
var item = ItemPrefab.Prefabs.GetRandom(p =>
p.CanBeSold && !RequestedGoods.Contains(p) &&
p.GetPriceInfo(this) is PriceInfo pi && pi.CanBeSpecial);
if (item == null) { break; }
RequestedGoods.Add(item);
}
StepsSinceSpecialsUpdated = 0;
}
public void AddToStock(List<SoldItem> items)
@@ -686,6 +987,20 @@ namespace Barotrauma
}
}
public static StoreBalanceStatus GetStoreBalanceStatus(int balance)
{
StoreBalanceStatus nextStatus = DefaultBalanceStatus;
foreach (var balanceStatus in StoreBalanceStatuses)
{
if (balanceStatus.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance &&
((float)balance / StoreInitialBalance) < balanceStatus.PercentageOfInitialBalance)
{
nextStatus = balanceStatus;
}
}
return nextStatus;
}
public XElement Save(Map map, XElement parentElement)
{
var locationElement = new XElement("location",
@@ -695,17 +1010,42 @@ namespace Barotrauma
new XAttribute("discovered", Discovered),
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
new XAttribute("pricemultiplier", PriceMultiplier),
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier));
if (ProximityTime.Length > 0 && ProximityTime.Any(t => t > 0))
{
locationElement.Add(new XAttribute("proximitytime", string.Join(',', ProximityTime.Select(i => i.ToString()))));
}
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier),
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange),
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation));
LevelData.Save(locationElement);
if (TypeChangeTimer > 0)
for (int i = 0; i < Type.CanChangeTo.Count; i++)
{
locationElement.Add(new XAttribute("changetimer", TypeChangeTimer));
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
{
if (ProximityTimer.ContainsKey(Type.CanChangeTo[i].Requirements[j]))
{
locationElement.Add(new XAttribute("proximitytimer" + i + "-" + j, ProximityTimer[Type.CanChangeTo[i].Requirements[j]]));
}
}
}
if (PendingLocationTypeChange.HasValue)
{
var changeElement = new XElement("pendinglocationtypechange", new XAttribute("timer", PendingLocationTypeChange.Value.delay));
if (PendingLocationTypeChange.Value.parentMission != null)
{
changeElement.Add(new XAttribute("missionidentifier", PendingLocationTypeChange.Value.parentMission.Identifier));
}
else
{
changeElement.Add(new XAttribute("index", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange)));
}
locationElement.Add(changeElement);
}
if (LocationTypeChangeCooldown > 0)
{
locationElement.Add(new XAttribute("locationtypechangecooldown", LocationTypeChangeCooldown));
}
if (takenItems.Any())
{
locationElement.Add(new XAttribute(
@@ -719,7 +1059,11 @@ namespace Barotrauma
if (StoreStock != null)
{
var storeElement = new XElement("store", new XAttribute("balance", StoreCurrentBalance));
var storeElement = new XElement("store",
new XAttribute("balance", StoreCurrentBalance),
new XAttribute("pricemodifier", StorePriceModifier),
new XAttribute("stepssincespecialsupdated", StepsSinceSpecialsUpdated));
foreach (PurchasedItem item in StoreStock)
{
if (item?.ItemPrefab == null) { continue; }
@@ -727,6 +1071,29 @@ namespace Barotrauma
new XAttribute("id", item.ItemPrefab.Identifier),
new XAttribute("qty", item.Quantity)));
}
if (DailySpecials.Any())
{
var dailySpecialElement = new XElement("dailyspecials");
foreach (var item in DailySpecials)
{
dailySpecialElement.Add(new XElement("item",
new XAttribute("id", item.Identifier)));
}
storeElement.Add(dailySpecialElement);
}
if (RequestedGoods.Any())
{
var requestedGoodsElement = new XElement("requestedgoods");
foreach (var item in RequestedGoods)
{
requestedGoodsElement.Add(new XElement("item",
new XAttribute("id", item.Identifier)));
}
storeElement.Add(requestedGoodsElement);
}
locationElement.Add(storeElement);
}
@@ -735,7 +1102,7 @@ namespace Barotrauma
var missionsElement = new XElement("missions");
foreach (Mission mission in missions)
{
var location = mission.Locations.FirstOrDefault(l => l != this);
var location = mission.Locations.All(l => l == this) ? this : mission.Locations.FirstOrDefault(l => l != this);
var i = map.Locations.IndexOf(location);
missionsElement.Add(new XElement("mission",
new XAttribute("prefabid", mission.Prefab.Identifier),
@@ -750,40 +1117,6 @@ namespace Barotrauma
return locationElement;
}
public int Distance(Location other, int maxRecursionDepth, int currRecursionDepth = 0)
{
if (currRecursionDepth >= maxRecursionDepth) { return -1; }
if (other == this) { return 0; }
int minDist = -1;
foreach (Location connected in Connections.Select(c => c.Locations.First(l => l != this)))
{
int dist = connected.Distance(other, maxRecursionDepth, currRecursionDepth+1);
if (dist >= 0)
{
if (minDist < 0 || dist < minDist) { minDist = dist; }
}
}
return minDist;
}
public void DetermineProximityTime(Location currentLocation)
{
int dist = Distance(currentLocation, Type.CanChangeTo.Select(cct => cct.RequiredProximityForProbabilityIncrease).Max());
for (int i=0;i<ProximityTime.Length;i++)
{
if (dist <= Type.CanChangeTo[i].RequiredProximityForProbabilityIncrease)
{
ProximityTime[i]++;
if (ProximityTime[i] > 5) { ProximityTime[i] = 5; }
}
else
{
ProximityTime[i]--;
if (ProximityTime[i] < 0) { ProximityTime[i] = 0; }
}
}
}
public void Remove()
{
RemoveProjSpecific();