Initial commit
This commit is contained in:
110
mod/PLibBuildings/BuildIngredient.cs
Normal file
110
mod/PLibBuildings/BuildIngredient.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// An ingredient to be used in a building.
|
||||
/// </summary>
|
||||
public class BuildIngredient {
|
||||
/// <summary>
|
||||
/// The material tag name.
|
||||
/// </summary>
|
||||
public string Material { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The quantity required in kg.
|
||||
/// </summary>
|
||||
public float Quantity { get; }
|
||||
|
||||
public BuildIngredient(string name, float quantity) {
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
if (quantity.IsNaNOrInfinity() || quantity <= 0.0f)
|
||||
throw new ArgumentException(nameof(quantity));
|
||||
Material = name;
|
||||
Quantity = quantity;
|
||||
}
|
||||
|
||||
public BuildIngredient(string[] material, int tier) : this(material[0], tier) { }
|
||||
|
||||
public BuildIngredient(string material, int tier) {
|
||||
// Intended for use from the MATERIALS and BUILDINGS presets
|
||||
if (string.IsNullOrEmpty(material))
|
||||
throw new ArgumentNullException(nameof(material));
|
||||
Material = material;
|
||||
float qty;
|
||||
switch (tier) {
|
||||
case -1:
|
||||
// Tier -1: 5
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER_TINY[0];
|
||||
break;
|
||||
case 0:
|
||||
// Tier 0: 25
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER0[0];
|
||||
break;
|
||||
case 1:
|
||||
// Tier 1: 50
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER1[0];
|
||||
break;
|
||||
case 2:
|
||||
// Tier 2: 100
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER2[0];
|
||||
break;
|
||||
case 3:
|
||||
// Tier 3: 200
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER3[0];
|
||||
break;
|
||||
case 4:
|
||||
// Tier 4: 400
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER4[0];
|
||||
break;
|
||||
case 5:
|
||||
// Tier 5: 800
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER5[0];
|
||||
break;
|
||||
case 6:
|
||||
// Tier 6: 1200
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER6[0];
|
||||
break;
|
||||
case 7:
|
||||
// Tier 7: 2000
|
||||
qty = TUNING.BUILDINGS.CONSTRUCTION_MASS_KG.TIER7[0];
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("tier must be between -1 and 7 inclusive");
|
||||
}
|
||||
Quantity = qty;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return obj is BuildIngredient other && other.Material == Material && other.
|
||||
Quantity == Quantity;
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return Material.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "Material[Tag={0},Quantity={1:F0}]".F(Material, Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
271
mod/PLibBuildings/ColoredRangeVisualizer.cs
Normal file
271
mod/PLibBuildings/ColoredRangeVisualizer.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// A visualizer that colors cells with an overlay when a building is selected or being
|
||||
/// previewed.
|
||||
/// </summary>
|
||||
public abstract class ColoredRangeVisualizer : KMonoBehaviour {
|
||||
/// <summary>
|
||||
/// The anim name to use when visualizing.
|
||||
/// </summary>
|
||||
private const string ANIM_NAME = "transferarmgrid_kanim";
|
||||
|
||||
/// <summary>
|
||||
/// The animations to play when the visualization is created.
|
||||
/// </summary>
|
||||
private static readonly HashedString[] PRE_ANIMS = new HashedString[] {
|
||||
"grid_pre",
|
||||
"grid_loop"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The animation to play when the visualization is destroyed.
|
||||
/// </summary>
|
||||
private static readonly HashedString POST_ANIM = "grid_pst";
|
||||
|
||||
/// <summary>
|
||||
/// The layer on which to display the visualizer.
|
||||
/// </summary>
|
||||
public Grid.SceneLayer Layer { get; set; }
|
||||
|
||||
// These components are automatically populated by KMonoBehaviour
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
#pragma warning disable CS0649
|
||||
[MyCmpGet]
|
||||
protected BuildingPreview preview;
|
||||
|
||||
[MyCmpGet]
|
||||
protected Rotatable rotatable;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
|
||||
/// <summary>
|
||||
/// The cells where animations are being displayed.
|
||||
/// </summary>
|
||||
private readonly HashSet<VisCellData> cells;
|
||||
|
||||
protected ColoredRangeVisualizer() {
|
||||
cells = new HashSet<VisCellData>();
|
||||
Layer = Grid.SceneLayer.FXFront;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates the visualizers as necessary.
|
||||
/// </summary>
|
||||
private void CreateVisualizers() {
|
||||
var visCells = HashSetPool<VisCellData, ColoredRangeVisualizer>.Allocate();
|
||||
var newCells = ListPool<VisCellData, ColoredRangeVisualizer>.Allocate();
|
||||
try {
|
||||
if (gameObject != null)
|
||||
VisualizeCells(visCells);
|
||||
// Destroy cells that are not used in the new one
|
||||
foreach (var cell in cells)
|
||||
if (visCells.Remove(cell))
|
||||
newCells.Add(cell);
|
||||
else
|
||||
cell.Destroy();
|
||||
// Newcomers get their controller created and added to the list
|
||||
foreach (var newCell in visCells) {
|
||||
newCell.CreateController(Layer);
|
||||
newCells.Add(newCell);
|
||||
}
|
||||
// Copy back to global
|
||||
cells.Clear();
|
||||
foreach (var cell in newCells)
|
||||
cells.Add(cell);
|
||||
} finally {
|
||||
visCells.Recycle();
|
||||
newCells.Recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when cells are changed in the building radius.
|
||||
/// </summary>
|
||||
private void OnCellChange() {
|
||||
CreateVisualizers();
|
||||
}
|
||||
|
||||
protected override void OnCleanUp() {
|
||||
Unsubscribe((int)GameHashes.SelectObject);
|
||||
if (preview != null) {
|
||||
Singleton<CellChangeMonitor>.Instance.UnregisterCellChangedHandler(transform,
|
||||
OnCellChange);
|
||||
if (rotatable != null)
|
||||
Unsubscribe((int)GameHashes.Rotated);
|
||||
}
|
||||
RemoveVisualizers();
|
||||
base.OnCleanUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object is rotated.
|
||||
/// </summary>
|
||||
private void OnRotated(object _) {
|
||||
CreateVisualizers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object is selected.
|
||||
/// </summary>
|
||||
/// <param name="data">true if selected, or false if deselected.</param>
|
||||
private void OnSelect(object data) {
|
||||
if (data is bool selected) {
|
||||
var position = transform.position;
|
||||
// Play the appropriate sound and update the visualizers
|
||||
if (selected) {
|
||||
PGameUtils.PlaySound("RadialGrid_form", position);
|
||||
CreateVisualizers();
|
||||
} else {
|
||||
PGameUtils.PlaySound("RadialGrid_disappear", position);
|
||||
RemoveVisualizers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSpawn() {
|
||||
base.OnSpawn();
|
||||
Subscribe((int)GameHashes.SelectObject, OnSelect);
|
||||
if (preview != null) {
|
||||
// Previews can be moved
|
||||
Singleton<CellChangeMonitor>.Instance.RegisterCellChangedHandler(transform,
|
||||
OnCellChange, nameof(ColoredRangeVisualizer) + ".OnSpawn");
|
||||
if (rotatable != null)
|
||||
Subscribe((int)GameHashes.Rotated, OnRotated);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all of the visualizers.
|
||||
/// </summary>
|
||||
private void RemoveVisualizers() {
|
||||
foreach (var cell in cells)
|
||||
cell.Destroy();
|
||||
cells.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the offset cell from the specified starting point, including the
|
||||
/// rotation of this object.
|
||||
/// </summary>
|
||||
/// <param name="baseCell">The starting cell.</param>
|
||||
/// <param name="offset">The offset if the building had its default rotation.</param>
|
||||
/// <returns>The computed destination cell.</returns>
|
||||
protected int RotateOffsetCell(int baseCell, CellOffset offset) {
|
||||
if (rotatable != null)
|
||||
offset = rotatable.GetRotatedCellOffset(offset);
|
||||
return Grid.OffsetCell(baseCell, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when cell visualizations need to be updated. Visualized cells should be
|
||||
/// added to the collection supplied as an argument.
|
||||
/// </summary>
|
||||
/// <param name="newCells">The cells which should be visualized.</param>
|
||||
protected abstract void VisualizeCells(ICollection<VisCellData> newCells);
|
||||
|
||||
/// <summary>
|
||||
/// Stores the data about a particular cell, including its anim controller and tint
|
||||
/// color.
|
||||
/// </summary>
|
||||
protected sealed class VisCellData : IComparable<VisCellData> {
|
||||
/// <summary>
|
||||
/// The target cell.
|
||||
/// </summary>
|
||||
public int Cell { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The anim controller for this cell.
|
||||
/// </summary>
|
||||
public KBatchedAnimController Controller { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tint used for this cell.
|
||||
/// </summary>
|
||||
public Color Tint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a visualized cell.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell to visualize.</param>
|
||||
public VisCellData(int cell) : this(cell, Color.white) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a visualized cell.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell to visualize.</param>
|
||||
/// <param name="tint">The color to tint it.</param>
|
||||
public VisCellData(int cell, Color tint) {
|
||||
Cell = cell;
|
||||
Controller = null;
|
||||
Tint = tint;
|
||||
}
|
||||
|
||||
public int CompareTo(VisCellData other) {
|
||||
if (other == null)
|
||||
throw new ArgumentNullException(nameof(other));
|
||||
return Cell.CompareTo(other.Cell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the anim controller for this cell.
|
||||
/// </summary>
|
||||
/// <param name="sceneLayer">The layer on which to display the animation.</param>
|
||||
public void CreateController(Grid.SceneLayer sceneLayer) {
|
||||
Controller = FXHelpers.CreateEffect(ANIM_NAME, Grid.CellToPosCCC(Cell,
|
||||
sceneLayer), null, false, sceneLayer, true);
|
||||
Controller.destroyOnAnimComplete = false;
|
||||
Controller.visibilityType = KAnimControllerBase.VisibilityType.Always;
|
||||
Controller.gameObject.SetActive(true);
|
||||
Controller.Play(PRE_ANIMS, KAnim.PlayMode.Loop);
|
||||
Controller.TintColour = Tint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the anim controller for this cell.
|
||||
/// </summary>
|
||||
public void Destroy() {
|
||||
if (Controller != null) {
|
||||
Controller.destroyOnAnimComplete = true;
|
||||
Controller.Play(POST_ANIM, KAnim.PlayMode.Once, 1f, 0f);
|
||||
Controller = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return obj is VisCellData other && other.Cell == Cell && Tint.Equals(other.
|
||||
Tint);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return Cell;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "CellData[cell={0:D},color={1}]".F(Cell, Tint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
mod/PLibBuildings/ConduitConnection.cs
Normal file
43
mod/PLibBuildings/ConduitConnection.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// Represents a pipe connection to a building.
|
||||
/// </summary>
|
||||
public class ConduitConnection {
|
||||
/// <summary>
|
||||
/// The conduit location.
|
||||
/// </summary>
|
||||
public CellOffset Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The conduit type.
|
||||
/// </summary>
|
||||
public ConduitType Type { get; }
|
||||
|
||||
public ConduitConnection(ConduitType type, CellOffset location) {
|
||||
Location = location;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("Connection[Type={0},Location={1}]", Type, Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
158
mod/PLibBuildings/PBuilding.Utils.cs
Normal file
158
mod/PLibBuildings/PBuilding.Utils.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// Utility methods for creating new buildings.
|
||||
/// </summary>
|
||||
public sealed partial class PBuilding {
|
||||
/// <summary>
|
||||
/// The default building category.
|
||||
/// </summary>
|
||||
private static readonly HashedString DEFAULT_CATEGORY = new HashedString("Base");
|
||||
|
||||
/// <summary>
|
||||
/// Makes the building always operational.
|
||||
/// </summary>
|
||||
/// <param name="go">The game object to configure.</param>
|
||||
private static void ApplyAlwaysOperational(GameObject go) {
|
||||
// Remove default components that could make a building non-operational
|
||||
if (go.TryGetComponent(out BuildingEnabledButton enabled))
|
||||
Object.DestroyImmediate(enabled);
|
||||
if (go.TryGetComponent(out Operational op))
|
||||
Object.DestroyImmediate(op);
|
||||
if (go.TryGetComponent(out LogicPorts lp))
|
||||
Object.DestroyImmediate(lp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logic port, in a method compatible with both the new and old Automation
|
||||
/// updates. The port will have the default strings which fit well with the
|
||||
/// LogicOperationalController.
|
||||
/// </summary>
|
||||
/// <returns>A logic port compatible with both editions.</returns>
|
||||
public static LogicPorts.Port CompatLogicPort(LogicPortSpriteType type,
|
||||
CellOffset offset) {
|
||||
return new LogicPorts.Port(LogicOperationalController.PORT_ID, offset,
|
||||
STRINGS.UI.LOGIC_PORTS.CONTROL_OPERATIONAL,
|
||||
STRINGS.UI.LOGIC_PORTS.CONTROL_OPERATIONAL_ACTIVE,
|
||||
STRINGS.UI.LOGIC_PORTS.CONTROL_OPERATIONAL_INACTIVE, false, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the building to the plan menu.
|
||||
/// </summary>
|
||||
public void AddPlan() {
|
||||
if (!addedPlan && Category.IsValid) {
|
||||
bool add = false;
|
||||
foreach (var menu in TUNING.BUILDINGS.PLANORDER)
|
||||
if (menu.category == Category) {
|
||||
AddPlanToCategory(menu);
|
||||
add = true;
|
||||
break;
|
||||
}
|
||||
if (!add)
|
||||
PUtil.LogWarning("Unable to find build menu: " + Category);
|
||||
addedPlan = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a building to a specific plan menu.
|
||||
/// </summary>
|
||||
/// <param name="menu">The menu to which to add the building.</param>
|
||||
private void AddPlanToCategory(PlanScreen.PlanInfo menu) {
|
||||
// Found category
|
||||
var data = menu.buildingAndSubcategoryData;
|
||||
if (data != null) {
|
||||
string addID = AddAfter;
|
||||
bool add = false;
|
||||
if (addID != null) {
|
||||
// Optionally choose the position
|
||||
int n = data.Count;
|
||||
for (int i = 0; i < n - 1 && !add; i++)
|
||||
if (data[i].Key == addID) {
|
||||
data.Insert(i + 1, new KeyValuePair<string, string>(ID,
|
||||
SubCategory));
|
||||
add = true;
|
||||
}
|
||||
}
|
||||
if (!add)
|
||||
data.Add(new KeyValuePair<string, string>(ID, SubCategory));
|
||||
} else
|
||||
PUtil.LogWarning("Build menu " + Category + " has invalid entries!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the building strings to the strings list.
|
||||
/// </summary>
|
||||
public void AddStrings() {
|
||||
if (!addedStrings) {
|
||||
string prefix = "STRINGS.BUILDINGS.PREFABS." + ID.ToUpperInvariant() + ".";
|
||||
string nameStr = prefix + "NAME";
|
||||
if (Strings.TryGet(nameStr, out StringEntry localized))
|
||||
Name = localized.String;
|
||||
else
|
||||
Strings.Add(nameStr, Name);
|
||||
// Allow null values to be defined in LocString class adds / etc
|
||||
if (Description != null)
|
||||
Strings.Add(prefix + "DESC", Description);
|
||||
if (EffectText != null)
|
||||
Strings.Add(prefix + "EFFECT", EffectText);
|
||||
addedStrings = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the building tech to the tech tree.
|
||||
/// </summary>
|
||||
public void AddTech() {
|
||||
if (!addedTech && Tech != null) {
|
||||
var technology = Db.Get().Techs?.TryGet(Tech);
|
||||
if (technology != null)
|
||||
technology.unlockedItemIDs?.Add(ID);
|
||||
addedTech = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits up logic input/output ports and configures the game object with them.
|
||||
/// </summary>
|
||||
/// <param name="go">The game object to configure.</param>
|
||||
private void SplitLogicPorts(GameObject go) {
|
||||
int n = LogicIO.Count;
|
||||
var inputs = new List<LogicPorts.Port>(n);
|
||||
var outputs = new List<LogicPorts.Port>(n);
|
||||
foreach (var port in LogicIO)
|
||||
if (port.spriteType == LogicPortSpriteType.Output)
|
||||
outputs.Add(port);
|
||||
else
|
||||
inputs.Add(port);
|
||||
// This works in both the old and new versions
|
||||
var ports = go.AddOrGet<LogicPorts>();
|
||||
if (inputs.Count > 0)
|
||||
ports.inputPortInfo = inputs.ToArray();
|
||||
if (outputs.Count > 0)
|
||||
ports.outputPortInfo = outputs.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
479
mod/PLibBuildings/PBuilding.cs
Normal file
479
mod/PLibBuildings/PBuilding.cs
Normal file
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// A class used for creating new buildings. Abstracts many of the details to allow them
|
||||
/// to be used across different game versions.
|
||||
/// </summary>
|
||||
public sealed partial class PBuilding {
|
||||
/// <summary>
|
||||
/// The building ID which should precede this building ID in the plan menu.
|
||||
/// </summary>
|
||||
public string AddAfter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the building is always operational.
|
||||
/// </summary>
|
||||
public bool AlwaysOperational { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The building's animation.
|
||||
/// </summary>
|
||||
public string Animation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio sounds used when placing/completing the building.
|
||||
/// </summary>
|
||||
public string AudioCategory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio volume used when placing/completing the building.
|
||||
/// </summary>
|
||||
public string AudioSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this building can break down.
|
||||
/// </summary>
|
||||
public bool Breaks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The build menu category.
|
||||
/// </summary>
|
||||
public HashedString Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The construction time in seconds on x1 speed.
|
||||
/// </summary>
|
||||
public float ConstructionTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The decor of this building.
|
||||
/// </summary>
|
||||
public EffectorValues Decor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The building description.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text describing the building's effect.
|
||||
/// </summary>
|
||||
public string EffectText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this building can entomb.
|
||||
/// </summary>
|
||||
public bool Entombs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The heat generation from the exhaust in kDTU/s.
|
||||
/// </summary>
|
||||
public float ExhaustHeatGeneration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this building can flood.
|
||||
/// </summary>
|
||||
public bool Floods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default priority of this building, with null to not add a priority.
|
||||
/// </summary>
|
||||
public int? DefaultPriority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The self-heating when active in kDTU/s.
|
||||
/// </summary>
|
||||
public float HeatGeneration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The building height.
|
||||
/// </summary>
|
||||
public int Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The building HP until it breaks down.
|
||||
/// </summary>
|
||||
public int HP { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ingredients required for construction.
|
||||
/// </summary>
|
||||
public IList<BuildIngredient> Ingredients { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The building ID.
|
||||
/// </summary>
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this building is an industrial machine.
|
||||
/// </summary>
|
||||
public bool IndustrialMachine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The input conduits.
|
||||
/// </summary>
|
||||
public IList<ConduitConnection> InputConduits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this building is (or can be) a solid tile.
|
||||
/// </summary>
|
||||
public bool IsSolidTile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The logic ports.
|
||||
/// </summary>
|
||||
public IList<LogicPorts.Port> LogicIO { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The building name.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The noise of this building (not used by Klei).
|
||||
/// </summary>
|
||||
public EffectorValues Noise { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The layer for this building.
|
||||
/// </summary>
|
||||
public ObjectLayer ObjectLayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The output conduits.
|
||||
/// </summary>
|
||||
public IList<ConduitConnection> OutputConduits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If null, the building does not overheat; otherwise, it overheats at this
|
||||
/// temperature in K.
|
||||
/// </summary>
|
||||
public float? OverheatTemperature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The location where this building may be built.
|
||||
/// </summary>
|
||||
public BuildLocationRule Placement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If null, the building has no power input; otherwise, it uses this much power.
|
||||
/// </summary>
|
||||
public PowerRequirement PowerInput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If null, the building has no power output; otherwise, it provides this much power.
|
||||
/// </summary>
|
||||
public PowerRequirement PowerOutput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directions this building can face.
|
||||
/// </summary>
|
||||
public PermittedRotations RotateMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scene layer for this building.
|
||||
/// </summary>
|
||||
public Grid.SceneLayer SceneLayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The subcategory for this building.
|
||||
///
|
||||
/// The base game currently defines the following:
|
||||
/// Base:
|
||||
/// ladders, tiles, printing pods, doors, storage, tubes, default
|
||||
/// Oxygen:
|
||||
/// producers, scrubbers
|
||||
/// Power:
|
||||
/// generators, wires, batteries, transformers, switches
|
||||
/// Food:
|
||||
/// cooking, farming, ranching
|
||||
/// Plumbing:
|
||||
/// bathroom, pipes, pumps, valves, sensors
|
||||
/// HVAC:
|
||||
/// pipes, pumps, valves, sensors
|
||||
/// Refining:
|
||||
/// materials, oil, advanced
|
||||
/// Medical:
|
||||
/// cleaning, hospital, wellness
|
||||
/// Furniture:
|
||||
/// bed, lights, dining, recreation, pots, sculpture, electronic decor, moulding,
|
||||
/// canvas, dispaly, monument, signs
|
||||
/// Equipment:
|
||||
/// research, exploration, work stations, suits general, oxygen masks, atmo suits,
|
||||
/// jet suits, lead suits
|
||||
/// Utilities:
|
||||
/// temperature, other utilities, special
|
||||
/// Automation:
|
||||
/// wires, sensors, logic gates, utilities
|
||||
/// Solid Transport:
|
||||
/// conduit, valves, utilities
|
||||
/// Rocketry:
|
||||
/// telescopes, launch pad, railguns, engines, fuel and oxidizer, cargo, utility,
|
||||
/// command, fittings
|
||||
/// Radiation:
|
||||
/// HEP, uranium, radiation
|
||||
/// </summary>
|
||||
public string SubCategory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The technology name required to unlock the building.
|
||||
/// </summary>
|
||||
public string Tech { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The view mode used when placing this building.
|
||||
/// </summary>
|
||||
public HashedString ViewMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The building width.
|
||||
/// </summary>
|
||||
public int Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the building was added to the plan menu.
|
||||
/// </summary>
|
||||
private bool addedPlan;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the strings were added.
|
||||
/// </summary>
|
||||
private bool addedStrings;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the technology wes added.
|
||||
/// </summary>
|
||||
private bool addedTech;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new building. All buildings thus created must be registered using
|
||||
/// PBuilding.Register and have an appropriate IBuildingConfig class.
|
||||
///
|
||||
/// Building should be created in OnLoad or a post-load patch (not in static
|
||||
/// initializers) to give the localization framework time to patch the LocString
|
||||
/// containing the building name and description.
|
||||
/// </summary>
|
||||
/// <param name="id">The building ID.</param>
|
||||
/// <param name="name">The building name.</param>
|
||||
public PBuilding(string id, string name) {
|
||||
if (string.IsNullOrEmpty(id))
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
AddAfter = null;
|
||||
AlwaysOperational = false;
|
||||
Animation = "";
|
||||
AudioCategory = "Metal";
|
||||
AudioSize = "medium";
|
||||
Breaks = true;
|
||||
Category = DEFAULT_CATEGORY;
|
||||
ConstructionTime = 10.0f;
|
||||
Decor = TUNING.BUILDINGS.DECOR.NONE;
|
||||
DefaultPriority = null;
|
||||
Description = "Default Building Description";
|
||||
EffectText = "Default Building Effect";
|
||||
Entombs = true;
|
||||
ExhaustHeatGeneration = 0.0f;
|
||||
Floods = true;
|
||||
HeatGeneration = 0.0f;
|
||||
Height = 1;
|
||||
Ingredients = new List<BuildIngredient>(4);
|
||||
IndustrialMachine = false;
|
||||
InputConduits = new List<ConduitConnection>(4);
|
||||
HP = 100;
|
||||
ID = id;
|
||||
LogicIO = new List<LogicPorts.Port>(4);
|
||||
Name = name;
|
||||
Noise = TUNING.NOISE_POLLUTION.NONE;
|
||||
ObjectLayer = PGameUtils.GetObjectLayer(nameof(ObjectLayer.Building), ObjectLayer.
|
||||
Building);
|
||||
OutputConduits = new List<ConduitConnection>(4);
|
||||
OverheatTemperature = null;
|
||||
Placement = BuildLocationRule.OnFloor;
|
||||
PowerInput = null;
|
||||
PowerOutput = null;
|
||||
RotateMode = PermittedRotations.Unrotatable;
|
||||
SceneLayer = Grid.SceneLayer.Building;
|
||||
// Hard coded strings in base game, no const to reference
|
||||
SubCategory = "default";
|
||||
Tech = null;
|
||||
ViewMode = OverlayModes.None.ID;
|
||||
Width = 1;
|
||||
|
||||
addedPlan = false;
|
||||
addedStrings = false;
|
||||
addedTech = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the building def from this class.
|
||||
/// </summary>
|
||||
/// <returns>The Klei building def.</returns>
|
||||
public BuildingDef CreateDef() {
|
||||
// The number of fields in BuildingDef makes it somewhat impractical to detour
|
||||
if (Width < 1)
|
||||
throw new InvalidOperationException("Building width: " + Width);
|
||||
if (Height < 1)
|
||||
throw new InvalidOperationException("Building height: " + Height);
|
||||
if (HP < 1)
|
||||
throw new InvalidOperationException("Building HP: " + HP);
|
||||
if (ConstructionTime.IsNaNOrInfinity())
|
||||
throw new InvalidOperationException("Construction time: " + ConstructionTime);
|
||||
// Build an ingredients list
|
||||
int n = Ingredients.Count;
|
||||
if (n < 1)
|
||||
throw new InvalidOperationException("No ingredients for build");
|
||||
float[] quantity = new float[n];
|
||||
string[] tag = new string[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
var ingredient = Ingredients[i];
|
||||
if (ingredient == null)
|
||||
throw new ArgumentNullException(nameof(ingredient));
|
||||
quantity[i] = ingredient.Quantity;
|
||||
tag[i] = ingredient.Material;
|
||||
}
|
||||
// Melting point is not currently used
|
||||
var def = BuildingTemplates.CreateBuildingDef(ID, Width, Height, Animation, HP,
|
||||
Math.Max(0.1f, ConstructionTime), quantity, tag, 2400.0f, Placement, Decor,
|
||||
Noise);
|
||||
// Solid tile?
|
||||
if (IsSolidTile) {
|
||||
//def.isSolidTile = true;
|
||||
def.BaseTimeUntilRepair = -1.0f;
|
||||
def.UseStructureTemperature = false;
|
||||
BuildingTemplates.CreateFoundationTileDef(def);
|
||||
}
|
||||
def.AudioCategory = AudioCategory;
|
||||
def.AudioSize = AudioSize;
|
||||
if (OverheatTemperature != null) {
|
||||
def.Overheatable = true;
|
||||
def.OverheatTemperature = OverheatTemperature ?? 348.15f;
|
||||
} else
|
||||
def.Overheatable = false;
|
||||
// Plug in
|
||||
if (PowerInput != null) {
|
||||
def.RequiresPowerInput = true;
|
||||
def.EnergyConsumptionWhenActive = PowerInput.MaxWattage;
|
||||
def.PowerInputOffset = PowerInput.PlugLocation;
|
||||
}
|
||||
// Plug out
|
||||
if (PowerOutput != null) {
|
||||
def.RequiresPowerOutput = true;
|
||||
def.GeneratorWattageRating = PowerOutput.MaxWattage;
|
||||
def.PowerOutputOffset = PowerOutput.PlugLocation;
|
||||
}
|
||||
def.Breakable = Breaks;
|
||||
def.PermittedRotations = RotateMode;
|
||||
def.ExhaustKilowattsWhenActive = ExhaustHeatGeneration;
|
||||
def.SelfHeatKilowattsWhenActive = HeatGeneration;
|
||||
def.Floodable = Floods;
|
||||
def.Entombable = Entombs;
|
||||
def.ObjectLayer = ObjectLayer;
|
||||
def.SceneLayer = SceneLayer;
|
||||
def.ViewMode = ViewMode;
|
||||
// Conduits (multiple per building are hard but will be added someday...)
|
||||
if (InputConduits.Count > 1)
|
||||
throw new InvalidOperationException("Only supports one input conduit");
|
||||
foreach (var conduit in InputConduits) {
|
||||
def.UtilityInputOffset = conduit.Location;
|
||||
def.InputConduitType = conduit.Type;
|
||||
}
|
||||
if (OutputConduits.Count > 1)
|
||||
throw new InvalidOperationException("Only supports one output conduit");
|
||||
foreach (var conduit in OutputConduits) {
|
||||
def.UtilityOutputOffset = conduit.Location;
|
||||
def.OutputConduitType = conduit.Type;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the building template of this building. Should be called in
|
||||
/// ConfigureBuildingTemplate.
|
||||
/// </summary>
|
||||
/// <param name="go">The game object to configure.</param>
|
||||
public void ConfigureBuildingTemplate(GameObject go) {
|
||||
if (AlwaysOperational)
|
||||
ApplyAlwaysOperational(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the logic ports of this building. Must be used <b>after</b> the
|
||||
/// PBuilding.DoPostConfigureComplete method if logic ports are required.
|
||||
///
|
||||
/// Should be called in DoPostConfigureComplete, DoPostConfigurePreview, and
|
||||
/// DoPostConfigureUnderConstruction.
|
||||
/// </summary>
|
||||
/// <param name="go">The game object to configure.</param>
|
||||
public void CreateLogicPorts(GameObject go) {
|
||||
SplitLogicPorts(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the post-configure complete steps that this building object can do.
|
||||
/// Not exhaustive! Other components must likely be added.
|
||||
///
|
||||
/// This method does NOT add the logic ports. Use CreateLogicPorts to do so,
|
||||
/// <b>after</b> this method has been invoked.
|
||||
/// </summary>
|
||||
/// <param name="go">The game object to configure.</param>
|
||||
public void DoPostConfigureComplete(GameObject go) {
|
||||
if (InputConduits.Count == 1) {
|
||||
var conduitConsumer = go.AddOrGet<ConduitConsumer>();
|
||||
foreach (var conduit in InputConduits) {
|
||||
conduitConsumer.alwaysConsume = true;
|
||||
conduitConsumer.conduitType = conduit.Type;
|
||||
conduitConsumer.wrongElementResult = ConduitConsumer.WrongElementResult.
|
||||
Store;
|
||||
}
|
||||
}
|
||||
if (OutputConduits.Count == 1) {
|
||||
var conduitDispenser = go.AddOrGet<ConduitDispenser>();
|
||||
foreach (var conduit in OutputConduits) {
|
||||
conduitDispenser.alwaysDispense = true;
|
||||
conduitDispenser.conduitType = conduit.Type;
|
||||
conduitDispenser.elementFilter = null;
|
||||
}
|
||||
}
|
||||
if (IndustrialMachine && go.TryGetComponent(out KPrefabID id))
|
||||
id.AddTag(RoomConstraints.ConstraintTags.IndustrialMachinery, false);
|
||||
if (PowerInput != null)
|
||||
go.AddOrGet<EnergyConsumer>();
|
||||
if (PowerOutput != null)
|
||||
go.AddOrGet<EnergyGenerator>();
|
||||
// Set a default priority
|
||||
if (DefaultPriority != null && go.TryGetComponent(out Prioritizable pr)) {
|
||||
Prioritizable.AddRef(go);
|
||||
pr.SetMasterPriority(new PrioritySetting(PriorityScreen.PriorityClass.basic,
|
||||
DefaultPriority ?? 5));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "PBuilding[ID={0}]".F(ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
208
mod/PLibBuildings/PBuildingManager.cs
Normal file
208
mod/PLibBuildings/PBuildingManager.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using HarmonyLib;
|
||||
using PeterHan.PLib.Core;
|
||||
using PeterHan.PLib.PatchManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// Manages PLib buildings to break down PBuilding into a more reasonable sized class.
|
||||
/// </summary>
|
||||
public sealed class PBuildingManager : PForwardedComponent {
|
||||
/// <summary>
|
||||
/// The version of this component. Uses the running PLib version.
|
||||
/// </summary>
|
||||
internal static readonly Version VERSION = new Version(PVersion.VERSION);
|
||||
|
||||
/// <summary>
|
||||
/// The instantiated copy of this class.
|
||||
/// </summary>
|
||||
internal static PBuildingManager Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Immediately adds an <i>existing</i> building ID to an existing technology ID in the
|
||||
/// tech tree.
|
||||
///
|
||||
/// Do <b>not</b> use this method on buildings registered through PBuilding as they
|
||||
/// are added automatically.
|
||||
///
|
||||
/// This method must be used in a Db.Initialize postfix patch or RunAt.AfterDbInit
|
||||
/// PPatchManager method/patch.
|
||||
/// </summary>
|
||||
/// <param name="tech">The technology tree node ID.</param>
|
||||
/// <param name="id">The building ID to add to that node.</param>
|
||||
public static void AddExistingBuildingToTech(string tech, string id) {
|
||||
if (string.IsNullOrEmpty(tech))
|
||||
throw new ArgumentNullException(nameof(tech));
|
||||
if (string.IsNullOrEmpty(id))
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
var technology = Db.Get().Techs?.TryGet(id);
|
||||
if (technology != null)
|
||||
technology.unlockedItemIDs?.Add(tech);
|
||||
}
|
||||
|
||||
private static void CreateBuildingDef_Postfix(BuildingDef __result, string anim,
|
||||
string id) {
|
||||
var animFiles = __result?.AnimFiles;
|
||||
if (animFiles != null && animFiles.Length > 0 && animFiles[0] == null)
|
||||
Debug.LogWarningFormat("(when looking for KAnim named {0} on building {1})",
|
||||
anim, id);
|
||||
}
|
||||
|
||||
private static void CreateEquipmentDef_Postfix(EquipmentDef __result, string Anim,
|
||||
string Id) {
|
||||
var anim = __result?.Anim;
|
||||
if (anim == null)
|
||||
Debug.LogWarningFormat("(when looking for KAnim named {0} on equipment {1})",
|
||||
Anim, Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message encountered by the PLib building system.
|
||||
/// </summary>
|
||||
/// <param name="message">The debug message.</param>
|
||||
internal static void LogBuildingDebug(string message) {
|
||||
Debug.LogFormat("[PLibBuildings] {0}", message);
|
||||
}
|
||||
|
||||
private static void LoadGeneratedBuildings_Prefix() {
|
||||
Instance?.AddAllStrings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The buildings which need to be registered.
|
||||
/// </summary>
|
||||
private readonly ICollection<PBuilding> buildings;
|
||||
|
||||
public override Version Version => VERSION;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a building manager to register PLib buildings.
|
||||
/// </summary>
|
||||
public PBuildingManager() {
|
||||
buildings = new List<PBuilding>(16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the strings for every registered building in all mods to the database.
|
||||
/// </summary>
|
||||
private void AddAllStrings() {
|
||||
InvokeAllProcess(0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the strings for each registered building in this mod to the database.
|
||||
/// </summary>
|
||||
private void AddStrings() {
|
||||
int n = buildings.Count;
|
||||
if (n > 0) {
|
||||
LogBuildingDebug("Register strings for {0:D} building(s) from {1}".F(n,
|
||||
Assembly.GetExecutingAssembly().GetNameSafe() ?? "?"));
|
||||
foreach (var building in buildings)
|
||||
if (building != null) {
|
||||
building.AddStrings();
|
||||
building.AddPlan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the techs for every registered building in all mods to the database.
|
||||
/// </summary>
|
||||
private void AddAllTechs() {
|
||||
InvokeAllProcess(1, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the techs for each registered building in this mod to the database.
|
||||
/// </summary>
|
||||
private void AddTechs() {
|
||||
int n = buildings.Count;
|
||||
if (n > 0) {
|
||||
LogBuildingDebug("Register techs for {0:D} building(s) from {1}".F(n,
|
||||
Assembly.GetExecutingAssembly().GetNameSafe() ?? "?"));
|
||||
foreach (var building in buildings)
|
||||
if (building != null) {
|
||||
building.AddTech();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a building to properly display its name, description, and tech tree
|
||||
/// entry. PLib must be initialized using InitLibrary before using this method. Each
|
||||
/// building should only be registered once, either in OnLoad or a post-load patch.
|
||||
/// </summary>
|
||||
/// <param name="building">The building to register.</param>
|
||||
public void Register(PBuilding building) {
|
||||
if (building == null)
|
||||
throw new ArgumentNullException(nameof(building));
|
||||
RegisterForForwarding();
|
||||
// Must use object as the building table type
|
||||
buildings.Add(building);
|
||||
#if DEBUG
|
||||
PUtil.LogDebug("Registered building: {0}".F(building.ID));
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Initialize(Harmony plibInstance) {
|
||||
Instance = this;
|
||||
|
||||
// Non essential, do not crash on fail
|
||||
try {
|
||||
plibInstance.Patch(typeof(BuildingTemplates), nameof(BuildingTemplates.
|
||||
CreateBuildingDef), postfix: PatchMethod(nameof(
|
||||
CreateBuildingDef_Postfix)));
|
||||
plibInstance.Patch(typeof(EquipmentTemplates), nameof(EquipmentTemplates.
|
||||
CreateEquipmentDef), postfix: PatchMethod(nameof(
|
||||
CreateEquipmentDef_Postfix)));
|
||||
} catch (Exception e) {
|
||||
#if DEBUG
|
||||
PUtil.LogExcWarn(e);
|
||||
#endif
|
||||
}
|
||||
plibInstance.Patch(typeof(GeneratedBuildings), nameof(GeneratedBuildings.
|
||||
LoadGeneratedBuildings), prefix: PatchMethod(nameof(
|
||||
LoadGeneratedBuildings_Prefix)));
|
||||
|
||||
// Avoid another Harmony patch by using PatchManager
|
||||
var pm = new PPatchManager(plibInstance);
|
||||
pm.RegisterPatch(RunAt.AfterDbInit, new BuildingTechRegistration());
|
||||
}
|
||||
|
||||
public override void Process(uint operation, object _) {
|
||||
if (operation == 0)
|
||||
AddStrings();
|
||||
else if (operation == 1)
|
||||
AddTechs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Patch Manager patch which registers all PBuilding technologies.
|
||||
/// </summary>
|
||||
private sealed class BuildingTechRegistration : IPatchMethodInstance {
|
||||
public void Run(Harmony instance) {
|
||||
Instance?.AddAllTechs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
mod/PLibBuildings/PLibBuildings.csproj
Normal file
20
mod/PLibBuildings/PLibBuildings.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Title>PLib Buildings</Title>
|
||||
<AssemblyTitle>PLib.Buildings</AssemblyTitle>
|
||||
<Version>4.11.0.0</Version>
|
||||
<UsesPLib>false</UsesPLib>
|
||||
<RootNamespace>PeterHan.PLib.Buildings</RootNamespace>
|
||||
<AssemblyVersion>4.11.0.0</AssemblyVersion>
|
||||
<DistributeMod>false</DistributeMod>
|
||||
<PLibCore>true</PLibCore>
|
||||
<Platforms>Vanilla;Mergedown</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\$(Platform)\Release\PLibBuildings.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../PLibCore/PLibCore.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
48
mod/PLibBuildings/PowerRequirement.cs
Normal file
48
mod/PLibBuildings/PowerRequirement.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
|
||||
namespace PeterHan.PLib.Buildings {
|
||||
/// <summary>
|
||||
/// Stores related information about a building's power requirements.
|
||||
/// </summary>
|
||||
public class PowerRequirement {
|
||||
/// <summary>
|
||||
/// The maximum building wattage.
|
||||
/// </summary>
|
||||
public float MaxWattage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the plug related to the foundation tile.
|
||||
/// </summary>
|
||||
public CellOffset PlugLocation { get; }
|
||||
|
||||
public PowerRequirement(float wattage, CellOffset plugLocation) {
|
||||
if (wattage.IsNaNOrInfinity() || wattage < 0.0f)
|
||||
throw new ArgumentException("wattage");
|
||||
MaxWattage = wattage;
|
||||
PlugLocation = plugLocation;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "Power[Watts={0:F0},Location={1}]".F(MaxWattage, PlugLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user