* vampire returns + transformstions redo * carcat fangs fix + greetings music update * vampire skill trees * Blood essence gathering Introduces the vampire blood essence mechanic, including the CP14SpellVampireGatherEssence spell, new skill point consumable component, and related UI/localization updates. Adds clientside effects for spell casting, new vampire skill and action, and refines skill point gain/loss popups. Also restructures vampire components, updates spell logic for client/server prediction, and removes unused parallax files. * perma damage * Add skill point cost to magic system and vampire essence spell Introduced CP14MagicEffectSkillPointCostComponent to allow magic effects to consume skill points. Updated the shared magic system to handle skill point checks and consumption. Added a new vampire spell for creating blood essence, including new icons and localization. Adjusted vampire component to grant and remove skill points, and updated related skill tree and spell prototypes. Minor fixes and refactoring in spell logic and descriptions. * blood step + blood vision skills * vampire clans icons * 50 players limit + vampire objectives * fixes * devourers altar transmutation * Remove StealTarget component from animal, dino, and mole NPCs The StealTarget component and associated stealGroup were removed from boar, dinosaur, and mole NPC definitions. This likely disables these entities from being targeted for stealing, possibly to adjust gameplay balance or fix unintended behavior. * fix * essence creation improve * altars * voice masks * transmutation fix * teleportation glyph * crimson candles * candle crafting * fix pointer predictions * Add Vampire Clan Battle gamemode and update vampire roles Introduces the 'Vampire Clan Battle' gamemode with new localization in English and Russian, updated game preset definitions, and secret weights. Refactors vampire antagonist briefings and objectives for multiple clans, adjusts vampire role preferences and team settings, and reduces the damage of the Vampire Gather Essence spell. Also includes minor improvements to spell and game mode descriptions, and corrects file naming for game preset locales. * powerful kicks in * time gates + vampire tree * vampire proto faction * fix * fixes * tree progression * search enemy * Update CP14SharedVampireSystem.cs * blood essence gathering redo * essence gathering refactor 2 * blood healing * Update secret_weights.yml * tree planting * boodgrass * tree upgrade announcement * construction graph integration * delete transmutation system * workbench crafting returns * cloaks crafting + cloak invisibility * make vampire tree is generic red tree (sad) * clan heart sprite * Refactor vampire tree to clan heart system Replaces the CP14VampireTreeComponent with CP14VampireClanHeartComponent, updating all related logic, appearance, and localization. Adjusts skill requirements, examination, and level progression to use the new clan heart system. Updates entity prototypes, visuals, and adds new orb sprites for clan heart levels. Localization strings and logic are updated to reflect the new terminology and mechanics. * Update SpeciesBlacklist.cs * Refactor vampire clan heart and remove tree spell Refactored the vampire clan heart to support essence regeneration over time and adjusted level thresholds. Removed the vampire tree planting spell and related prototype fields, as well as unused tree system code. Updated localization, entity prototypes, and faction definitions to reflect these changes. * Add clan heart construction for vampire clans Introduces construction graphs, entities, and conditions for building unique clan hearts for each vampire clan (Unnameable, Devourers, NightChildrens). Adds new construction conditions (all clan vampires required, singleton enforcement), updates skill tree to unlock constructions, and removes the now-obsolete CP14MagicEffectAllVampireClanRequiredComponent. Also adds new frame sprites and updates localization and prototype files accordingly. * level up vfx * VFX + lobby track * orb resprite * sprites * Add vampire altar mechanics and improve clan heart behavior Introduces the CP14VampireAltarComponent and altar entity, which doubles blood essence extraction when victims are strapped to the altar. Adds a custom explosion behavior for vampire clan hearts upon destruction, updates construction graphs and recipes for altars, and improves localization. Also refines skill description handling and adjusts vampire bite action text. * essence get when heart destruction * Add clan heart damage and destruction announcements Introduces announcements for when a vampire clan heart is damaged or destroyed, with cooldown to prevent spam. Refactors examination logic and updates localization files for both English and Russian to support new messages and sender formatting. * glyph adaptation * resurrection * Add round end summary for Vampire Clan Battles Implemented detailed round end text for the Vampire Clan Battles game mode, displaying victory, defeat, or draw outcomes based on surviving factions and population percentage. Refactored alive player percentage logic into a shared method and updated localization files with new outcome messages in English and Russian. Also removed an unused field from the defence condition component. * Update vampire_cloak.yml * fix * fix * Update portal_glyph.yml
600 lines
23 KiB
C#
600 lines
23 KiB
C#
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Construction.Components;
|
|
using Content.Shared._CP14.Workbench.Prototypes;
|
|
using Content.Shared.ActionBlocker;
|
|
using Content.Shared.Construction;
|
|
using Content.Shared.Construction.Prototypes;
|
|
using Content.Shared.Construction.Steps;
|
|
using Content.Shared.Coordinates;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.Hands.EntitySystems;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Storage;
|
|
using Content.Shared.Whitelist;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Server.Construction
|
|
{
|
|
public sealed partial class ConstructionSystem
|
|
{
|
|
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
|
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
|
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
|
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
|
|
|
// --- WARNING! LEGACY CODE AHEAD! ---
|
|
// This entire file contains the legacy code for initial construction.
|
|
// This is bound to be replaced by a better alternative (probably using dummy entities)
|
|
// but for now I've isolated them in their own little file. This code is largely unchanged.
|
|
// --- YOU HAVE BEEN WARNED! AAAH! ---
|
|
|
|
private readonly Dictionary<ICommonSession, HashSet<int>> _beingBuilt = new();
|
|
|
|
private void InitializeInitial()
|
|
{
|
|
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
|
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
|
}
|
|
|
|
// LEGACY CODE. See warning at the top of the file!
|
|
private IEnumerable<EntityUid> EnumerateNearby(EntityUid user)
|
|
{
|
|
foreach (var item in _handsSystem.EnumerateHeld(user))
|
|
{
|
|
if (TryComp(item, out StorageComponent? storage))
|
|
{
|
|
foreach (var storedEntity in storage.Container.ContainedEntities!)
|
|
{
|
|
yield return storedEntity;
|
|
}
|
|
}
|
|
|
|
yield return item;
|
|
}
|
|
|
|
if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator))
|
|
{
|
|
while (containerSlotEnumerator.MoveNext(out var containerSlot))
|
|
{
|
|
if(!containerSlot.ContainedEntity.HasValue)
|
|
continue;
|
|
|
|
if (TryComp(containerSlot.ContainedEntity.Value, out StorageComponent? storage))
|
|
{
|
|
foreach (var storedEntity in storage.Container.ContainedEntities)
|
|
{
|
|
yield return storedEntity;
|
|
}
|
|
}
|
|
|
|
yield return containerSlot.ContainedEntity.Value;
|
|
}
|
|
}
|
|
|
|
var pos = _transformSystem.GetMapCoordinates(user);
|
|
|
|
foreach (var near in _lookupSystem.GetEntitiesInRange(pos, 2f, LookupFlags.Contained | LookupFlags.Dynamic | LookupFlags.Sundries | LookupFlags.Approximate))
|
|
{
|
|
if (near == user)
|
|
continue;
|
|
if (_interactionSystem.InRangeUnobstructed(pos, near, 2f) && _container.IsInSameOrParentContainer(user, near))
|
|
yield return near;
|
|
}
|
|
}
|
|
|
|
// LEGACY CODE. See warning at the top of the file!
|
|
private async Task<EntityUid?> Construct(
|
|
EntityUid user,
|
|
string materialContainer,
|
|
ConstructionGraphPrototype graph,
|
|
ConstructionGraphEdge edge,
|
|
ConstructionGraphNode targetNode,
|
|
EntityCoordinates coords,
|
|
Angle angle = default)
|
|
{
|
|
// We need a place to hold our construction items!
|
|
var container = _container.EnsureContainer<Container>(user, materialContainer, out var existed);
|
|
|
|
if (existed)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-construct-cannot-start-another-construction"), user, user);
|
|
return null;
|
|
}
|
|
|
|
var containers = new Dictionary<string, Container>();
|
|
|
|
var doAfterTime = 0f;
|
|
|
|
// HOLY SHIT THIS IS SOME HACKY CODE.
|
|
// But I'd rather do this shit than risk having collisions with other containers.
|
|
Container GetContainer(string name)
|
|
{
|
|
if (containers.TryGetValue(name, out var container1))
|
|
return container1;
|
|
|
|
while (true)
|
|
{
|
|
var random = _robustRandom.Next();
|
|
var c = _container.EnsureContainer<Container>(user, random.ToString(), out var exists);
|
|
|
|
if (exists)
|
|
continue;
|
|
|
|
containers[name] = c;
|
|
return c;
|
|
}
|
|
}
|
|
|
|
void FailCleanup()
|
|
{
|
|
foreach (var entity in container.ContainedEntities.ToArray())
|
|
{
|
|
_container.Remove(entity, container);
|
|
}
|
|
|
|
foreach (var cont in containers.Values)
|
|
{
|
|
foreach (var entity in cont.ContainedEntities.ToArray())
|
|
{
|
|
_container.Remove(entity, cont);
|
|
}
|
|
}
|
|
|
|
// If we don't do this, items are invisible for some fucking reason. Nice.
|
|
Timer.Spawn(1, ShutdownContainers);
|
|
}
|
|
|
|
void ShutdownContainers()
|
|
{
|
|
_container.ShutdownContainer(container);
|
|
foreach (var c in containers.Values.ToArray())
|
|
{
|
|
_container.ShutdownContainer(c);
|
|
}
|
|
}
|
|
|
|
var failed = false;
|
|
|
|
var steps = new List<ConstructionGraphStep>();
|
|
var used = new HashSet<EntityUid>();
|
|
|
|
foreach (var step in edge.Steps)
|
|
{
|
|
doAfterTime += step.DoAfter;
|
|
|
|
var handled = false;
|
|
|
|
switch (step)
|
|
{
|
|
case MaterialConstructionGraphStep materialStep:
|
|
foreach (var entity in EnumerateNearby(user))
|
|
{
|
|
if (!materialStep.EntityValid(entity, out var stack))
|
|
continue;
|
|
|
|
if (used.Contains(entity))
|
|
continue;
|
|
|
|
// TODO allow taking from several stacks.
|
|
// Also update crafting steps to check if it works.
|
|
var splitStack = _stackSystem.Split(entity, materialStep.Amount, user.ToCoordinates(0, 0), stack);
|
|
|
|
if (splitStack == null)
|
|
continue;
|
|
|
|
if (string.IsNullOrEmpty(materialStep.Store))
|
|
{
|
|
if (!_container.Insert(splitStack.Value, container))
|
|
continue;
|
|
}
|
|
else if (!_container.Insert(splitStack.Value, GetContainer(materialStep.Store)))
|
|
continue;
|
|
|
|
handled = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case ArbitraryInsertConstructionGraphStep arbitraryStep:
|
|
foreach (var entity in new HashSet<EntityUid>(EnumerateNearby(user)))
|
|
{
|
|
if (!arbitraryStep.EntityValid(entity, EntityManager, Factory))
|
|
continue;
|
|
|
|
if (used.Contains(entity))
|
|
continue;
|
|
|
|
// Dump out any stored entities in used entity
|
|
if (TryComp<StorageComponent>(entity, out var storage))
|
|
{
|
|
_container.EmptyContainer(storage.Container);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(arbitraryStep.Store))
|
|
{
|
|
if (!_container.Insert(entity, container))
|
|
continue;
|
|
}
|
|
else if (!_container.Insert(entity, GetContainer(arbitraryStep.Store)))
|
|
continue;
|
|
|
|
handled = true;
|
|
used.Add(entity);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
//CP14 stack group support
|
|
case CP14StackGroupConstructionGraphStep stackGroupStep:
|
|
foreach (var entity in new HashSet<EntityUid>(EnumerateNearby(user)))
|
|
{
|
|
if (!stackGroupStep.EntityValid(entity, out var stack))
|
|
continue;
|
|
|
|
if (used.Contains(entity))
|
|
continue;
|
|
|
|
var splitStack = _stackSystem.Split(entity, stackGroupStep.Amount, user.ToCoordinates(0, 0), stack);
|
|
|
|
if (splitStack == null)
|
|
continue;
|
|
|
|
if (string.IsNullOrEmpty(stackGroupStep.Store))
|
|
{
|
|
if (!_container.Insert(splitStack.Value, container))
|
|
continue;
|
|
}
|
|
else if (!_container.Insert(splitStack.Value, GetContainer(stackGroupStep.Store)))
|
|
continue;
|
|
|
|
handled = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
//CP14 stack group support end
|
|
}
|
|
|
|
if (handled == false)
|
|
{
|
|
failed = true;
|
|
break;
|
|
}
|
|
|
|
steps.Add(step);
|
|
}
|
|
|
|
if (failed)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-construct-no-materials"), user, user);
|
|
FailCleanup();
|
|
return null;
|
|
}
|
|
|
|
var doAfterArgs = new DoAfterArgs(EntityManager, user, doAfterTime, new AwaitedDoAfterEvent(), null)
|
|
{
|
|
BreakOnDamage = true,
|
|
BreakOnMove = true,
|
|
NeedHand = false,
|
|
// allow simultaneously starting several construction jobs using the same stack of materials.
|
|
CancelDuplicate = false,
|
|
BlockDuplicate = false,
|
|
};
|
|
|
|
if (await _doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled)
|
|
{
|
|
FailCleanup();
|
|
return null;
|
|
}
|
|
|
|
var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager));
|
|
var newEntity = SpawnAttachedTo(newEntityProto, coords, rotation: angle);
|
|
|
|
if (!TryComp(newEntity, out ConstructionComponent? construction))
|
|
{
|
|
Log.Error($"Initial construction does not have a valid target entity! It is missing a ConstructionComponent.\nGraph: {graph.ID}, Initial Target: {edge.Target}, Ent. Prototype: {newEntityProto}\nCreated Entity {ToPrettyString(newEntity)} will be deleted.");
|
|
Del(newEntity); // Screw you, make proper construction graphs.
|
|
return null;
|
|
}
|
|
|
|
// We attempt to set the pathfinding target.
|
|
SetPathfindingTarget(newEntity, targetNode.Name, construction);
|
|
|
|
// We preserve the containers...
|
|
foreach (var (name, cont) in containers)
|
|
{
|
|
var newCont = _container.EnsureContainer<Container>(newEntity, name);
|
|
|
|
foreach (var entity in cont.ContainedEntities.ToArray())
|
|
{
|
|
_container.Remove(entity, cont, reparent: false, force: true);
|
|
_container.Insert(entity, newCont);
|
|
}
|
|
}
|
|
|
|
// We now get rid of all them.
|
|
ShutdownContainers();
|
|
|
|
// We have step completed steps!
|
|
foreach (var step in steps)
|
|
{
|
|
foreach (var completed in step.Completed)
|
|
{
|
|
completed.PerformAction(newEntity, user, EntityManager);
|
|
}
|
|
}
|
|
|
|
// And we also have edge completed effects!
|
|
foreach (var completed in edge.Completed)
|
|
{
|
|
completed.PerformAction(newEntity, user, EntityManager);
|
|
}
|
|
|
|
return newEntity;
|
|
}
|
|
|
|
private async void HandleStartItemConstruction(TryStartItemConstructionMessage ev, EntitySessionEventArgs args)
|
|
{
|
|
if (args.SenderSession.AttachedEntity is {Valid: true} user)
|
|
await TryStartItemConstruction(ev.PrototypeName, user);
|
|
}
|
|
|
|
// LEGACY CODE. See warning at the top of the file!
|
|
public async Task<bool> TryStartItemConstruction(string prototype, EntityUid user)
|
|
{
|
|
if (!PrototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
|
{
|
|
Log.Error($"Tried to start construction of invalid recipe '{prototype}'!");
|
|
return false;
|
|
}
|
|
|
|
if (!PrototypeManager.TryIndex(constructionPrototype.Graph,
|
|
out ConstructionGraphPrototype? constructionGraph))
|
|
{
|
|
Log.Error(
|
|
$"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{prototype}'!");
|
|
return false;
|
|
}
|
|
|
|
if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user);
|
|
return false;
|
|
}
|
|
|
|
//CP14 requirements
|
|
foreach (var req in constructionPrototype.CP14Restrictions)
|
|
{
|
|
if (!req.Check(EntityManager, user))
|
|
{
|
|
_popup.PopupEntity(req.GetDescription(EntityManager, PrototypeManager), user, user);
|
|
return false;
|
|
}
|
|
}
|
|
//CP14 end
|
|
|
|
var startNode = constructionGraph.Nodes[constructionPrototype.StartNode];
|
|
var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode];
|
|
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
|
|
|
|
if (!_actionBlocker.CanInteract(user, null))
|
|
return false;
|
|
|
|
if (!HasComp<HandsComponent>(user))
|
|
return false;
|
|
|
|
foreach (var condition in constructionPrototype.Conditions)
|
|
{
|
|
if (!condition.Condition(user, user.ToCoordinates(0, 0), Direction.South))
|
|
return false;
|
|
}
|
|
|
|
if (pathFind == null)
|
|
{
|
|
throw new InvalidDataException(
|
|
$"Can't find path from starting node to target node in construction! Recipe: {prototype}");
|
|
}
|
|
|
|
var edge = startNode.GetEdge(pathFind[0].Name);
|
|
|
|
if (edge == null)
|
|
{
|
|
throw new InvalidDataException(
|
|
$"Can't find edge from starting node to the next node in pathfinding! Recipe: {prototype}");
|
|
}
|
|
|
|
// No support for conditions here!
|
|
|
|
foreach (var step in edge.Steps)
|
|
{
|
|
switch (step)
|
|
{
|
|
case ToolConstructionGraphStep _:
|
|
throw new InvalidDataException("Invalid first step for construction recipe!");
|
|
}
|
|
}
|
|
|
|
if (await Construct(
|
|
user,
|
|
"item_construction",
|
|
constructionGraph,
|
|
edge,
|
|
targetNode,
|
|
Transform(user).Coordinates) is not { Valid: true } item)
|
|
return false;
|
|
|
|
// Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up
|
|
// or drop the item as normal.
|
|
_stackSystem.TryMergeToHands(item, user);
|
|
return true;
|
|
}
|
|
|
|
// LEGACY CODE. See warning at the top of the file!
|
|
private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args)
|
|
{
|
|
if (!PrototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
|
{
|
|
Log.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!");
|
|
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
|
return;
|
|
}
|
|
|
|
if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
|
{
|
|
Log.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!");
|
|
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
|
return;
|
|
}
|
|
|
|
if (args.SenderSession.AttachedEntity is not {Valid: true} user)
|
|
{
|
|
Log.Error($"Client sent {nameof(TryStartStructureConstructionMessage)} with no attached entity!");
|
|
return;
|
|
}
|
|
|
|
if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user);
|
|
return;
|
|
}
|
|
|
|
//CP14 requirements
|
|
foreach (var req in constructionPrototype.CP14Restrictions)
|
|
{
|
|
if (!req.Check(EntityManager, user))
|
|
{
|
|
_popup.PopupEntity(req.GetDescription(EntityManager, PrototypeManager), user, user);
|
|
return;
|
|
}
|
|
}
|
|
//CP14 end
|
|
|
|
if (_container.IsEntityInContainer(user))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-inside-container"), user, user);
|
|
return;
|
|
}
|
|
|
|
var startNode = constructionGraph.Nodes[constructionPrototype.StartNode];
|
|
var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode];
|
|
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
|
|
|
|
|
|
if (_beingBuilt.TryGetValue(args.SenderSession, out var set))
|
|
{
|
|
if (!set.Add(ev.Ack))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("construction-system-already-building"), user, user);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var newSet = new HashSet<int> {ev.Ack};
|
|
_beingBuilt[args.SenderSession] = newSet;
|
|
}
|
|
|
|
var location = GetCoordinates(ev.Location);
|
|
|
|
foreach (var condition in constructionPrototype.Conditions)
|
|
{
|
|
if (!condition.Condition(user, location, ev.Angle.GetCardinalDir()))
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Cleanup()
|
|
{
|
|
_beingBuilt[args.SenderSession].Remove(ev.Ack);
|
|
}
|
|
|
|
if (!_actionBlocker.CanInteract(user, null)
|
|
|| !TryComp(user, out HandsComponent? hands) || _handsSystem.GetActiveItem((user, hands)) == null)
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
|
|
var mapPos = _transformSystem.ToMapCoordinates(location);
|
|
var predicate = GetPredicate(constructionPrototype.CanBuildInImpassable, mapPos);
|
|
|
|
if (!_interactionSystem.InRangeUnobstructed(user, mapPos, predicate: predicate))
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
|
|
if (pathFind == null)
|
|
throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}");
|
|
|
|
var edge = startNode.GetEdge(pathFind[0].Name);
|
|
|
|
if(edge == null)
|
|
throw new InvalidDataException($"Can't find edge from starting node to the next node in pathfinding! Recipe: {ev.PrototypeName}");
|
|
|
|
var valid = false;
|
|
|
|
if (_handsSystem.GetActiveItem((user, hands)) is not {Valid: true} holding)
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
|
|
// No support for conditions here!
|
|
|
|
foreach (var step in edge.Steps)
|
|
{
|
|
switch (step)
|
|
{
|
|
case EntityInsertConstructionGraphStep entityInsert:
|
|
if (entityInsert.EntityValid(holding, EntityManager, Factory))
|
|
valid = true;
|
|
break;
|
|
case ToolConstructionGraphStep _:
|
|
throw new InvalidDataException("Invalid first step for item recipe!");
|
|
}
|
|
|
|
if (valid)
|
|
break;
|
|
}
|
|
|
|
if (!valid)
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
|
|
if (await Construct(user,
|
|
(ev.Ack + constructionPrototype.GetHashCode()).ToString(),
|
|
constructionGraph,
|
|
edge,
|
|
targetNode,
|
|
GetCoordinates(ev.Location),
|
|
constructionPrototype.CanRotate ? ev.Angle : Angle.Zero) is not {Valid: true} structure)
|
|
{
|
|
Cleanup();
|
|
return;
|
|
}
|
|
|
|
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack, GetNetEntity(structure)));
|
|
_adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(user):player} has turned a {ev.PrototypeName} construction ghost into {ToPrettyString(structure)} at {Transform(structure).Coordinates}");
|
|
Cleanup();
|
|
}
|
|
}
|
|
}
|