private void ChangePipeObjectToTile() { Orientation orientation = GetComponent <Directional>().CurrentDirection; // Spawn the correct disposal pipe tile, based on current orientation. DisposalPipe pipeTileToSpawn = GetPipeTileByOrientation(orientation); if (pipeTileToSpawn != null) { var matrixTransform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one); Color pipeColor = GetComponentInChildren <SpriteRenderer>().color; registerTile.Matrix.TileChangeManager.UpdateTile(registerTile.LocalPositionServer, pipeTileToSpawn, matrixTransform, pipeColor); _ = Despawn.ServerSingle(gameObject); } else { Logger.LogError($"Failed to spawn disposal pipe tile! Is {name} missing reference to tile asset for {orientation}?", Category.Pipes); } }
public override void TryConsume(GameObject feederGO, GameObject eaterGO) { var eater = eaterGO.GetComponent <PlayerScript>(); if (eater == null) { // todo: implement non-player eating SoundManager.PlayNetworkedAtPos(sound, item.WorldPosition); if (leavings != null) { Spawn.ServerPrefab(leavings, item.WorldPosition, transform.parent); } _ = Despawn.ServerSingle(gameObject); return; } var feeder = feederGO.GetComponent <PlayerScript>(); // Show eater message var eaterHungerState = eater.playerHealth.HungerState; ConsumableTextUtils.SendGenericConsumeMessage(feeder, eater, eaterHungerState, Name, "eat"); // Check if eater can eat anything if (eaterHungerState != HungerState.Full) { if (feeder != eater) //If you're feeding it to someone else. { //Wait 3 seconds before you can feed StandardProgressAction.Create(ProgressConfig, () => { ConsumableTextUtils.SendGenericForceFeedMessage(feeder, eater, eaterHungerState, Name, "eat"); Eat(eater, feeder); }).ServerStartProgress(eater.registerTile, 3f, feeder.gameObject); return; } Eat(eater, feeder); } }
private void CheckCooked() { /* -- Obsolete, only affects one item. * if (storedCookable == null) return; * * // True if the item's total cooking time exceeds the item's minimum cooking time. * if (storedCookable.AddCookingTime(Time.deltaTime*LaserTierTimeEffect())) * { * // Swap item for its cooked version, if applicable. * * if (storedCookable.CookedProduct == null) return; * * Despawn.ServerSingle(storedCookable.gameObject); * GameObject cookedItem = Spawn.ServerPrefab(storedCookable.CookedProduct).GameObject; * Inventory.ServerAdd(cookedItem, storageSlot); * } */ foreach (var slot in storage.GetItemSlots()) { if (slot.IsOccupied == true) { if (slot.ItemObject.TryGetComponent(out Cookable slotCooked)) { // True if the item's total cooking time exceeds the item's minimum cooking time. if (slotCooked.AddCookingTime(Time.deltaTime * LaserTierTimeEffect()) == true) { // Swap item for its cooked version, if applicable. if (slotCooked.CookedProduct == null) { return; } Despawn.ServerSingle(slotCooked.gameObject); GameObject cookedItem = Spawn.ServerPrefab(slotCooked.CookedProduct).GameObject; Inventory.ServerAdd(cookedItem, slot); } } } } }
private void InitialStateInteract(HandApply interaction) { if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Cable)) { ToolUtils.ServerUseToolWithActionMessages(interaction, 2f, "You start adding cables to the frame...", $"{interaction.Performer.ExpensiveName()} starts adding cables to the frame...", "You add cables to the frame.", $"{interaction.Performer.ExpensiveName()} adds cables to the frame.", () => { Inventory.ServerConsume(interaction.HandSlot, 1); ServerSetState(State.wiresAdded); }); } else if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Wrench)) { Spawn.ServerPrefab(FixtureFrameItemPrefab, transform.position, interaction.Performer.transform.parent, spawnItems: false); _ = Despawn.ServerSingle(gameObject); } }
private void Flip() { SpawnResult flippedObjectSpawn = Spawn.ServerPrefab(flippedObject, gameObject.RegisterTile().WorldPositionServer); if (flippedObjectSpawn.Successful) { if (flippedObjectSpawn.GameObject.TryGetComponent(out Directional directional)) { var initialOrientation = directional.CurrentDirection; directional.FaceDirection(initialOrientation); } Despawn.ServerSingle(gameObject); } else { Logger.LogError( $"Failed to spawn {name}'s flipped version! " + $"Is {name} missing reference to {nameof(flippedObject)} prefab?"); } }
private void TryAddBulb(HandApply interaction) { if (mState != LightMountState.MissingBulb) { return; } if (Validations.HasItemTrait(interaction.HandObject, CommonTraits.Instance.Broken)) { ServerChangeLightState(LightMountState.Broken); } else { ServerChangeLightState( (switchState && (powerState == PowerState.On)) ? LightMountState.On : (powerState != PowerState.OverVoltage) ? LightMountState.Emergency : LightMountState.Off); } _ = Despawn.ServerSingle(interaction.HandObject); }
/// <summary> /// For use when player is connected and dead. /// Respawns the mind's character and transfers their control to it. /// </summary> /// <param name="forMind"></param> public static void ServerRespawnPlayer(Mind forMind) { if (forMind.IsSpectator) { return; } //get the settings from the mind var occupation = forMind.occupation; var oldBody = forMind.GetCurrentMob(); var connection = oldBody.GetComponent <NetworkIdentity>().connectionToClient; var settings = oldBody.GetComponent <PlayerScript>().characterSettings; var oldGhost = forMind.ghost; ServerSpawnInternal(connection, occupation, settings, forMind, willDestroyOldBody: oldGhost != null); if (oldGhost) { Despawn.ServerSingle(oldGhost.gameObject); } }
public void ServerPerformInteraction(HandApply interaction) { //unscrew ToolUtils.ServerUseToolWithActionMessages(interaction, secondsToScrewdrive, "You start to disconnect the monitor...", $"{interaction.Performer.ExpensiveName()} starts to disconnect the monitor...", "You disconnect the monitor.", $"{interaction.Performer.ExpensiveName()} disconnects the monitor.", () => { //drop all our contents var itemStorage = GetComponent <ItemStorage>(); if (itemStorage != null) { itemStorage.ServerDropAll(); } var frame = Spawn.ServerPrefab(framePrefab, SpawnDestination.At(gameObject)).GameObject; frame.GetComponent <ComputerFrame>().ServerInitFromComputer(this); Despawn.ServerSingle(gameObject); }); }
private void Unwrench(HandApply interaction) { if (SpawnOnDeconstruct == null) { Logger.LogError($"{this} is missing reference to {nameof(SpawnOnDeconstruct)}!", Category.Interaction); return; } var spawn = Spawn.ServerPrefab(SpawnOnDeconstruct, registerTile.WorldPositionServer, localRotation: transform.localRotation); spawn.GameObject.GetComponent <PipeItem>().SetColour(Colour); if (directional != null && spawn.GameObject.TryGetComponent <PlayerRotatable>(out var newDirectional)) { newDirectional.SyncRotation(0, transform.eulerAngles.z); } OnDisassembly(interaction); pipeData.OnDisable(); _ = Despawn.ServerSingle(gameObject); }
/// <summary> /// Despawn bullet and call all /// on despawn behaviours /// </summary> private void DespawnThis(MatrixManager.CustomPhysicsHit hit, Vector2 point) { destroyed = true; foreach (var behaviour in behavioursOnBulletDespawn) { behaviour.OnDespawn(hit, point); } if (CustomNetworkManager.IsServer) { _ = Despawn.ServerSingle(gameObject); } else { if (Despawn.ClientSingle(gameObject).Successful == false) { Destroy(gameObject); } } }
public void ServerPerformInteraction(PositionalHandApply interaction) { //wirecutters can be used to cut this cable Vector3Int worldPosInt = interaction.WorldPositionTarget.To2Int().To3Int(); MatrixInfo matrix = MatrixManager.AtPoint(worldPosInt, true); var localPosInt = MatrixManager.WorldToLocalInt(worldPosInt, matrix); if (matrix.Matrix != null) { if (!matrix.Matrix.IsClearUnderfloorConstruction(localPosInt, true)) { return; } } else { return; } Spawn.ServerPrefab("Medium machine connector", gameObject.AssumedWorldPosServer()); Despawn.ServerSingle(gameObject); }
private void Update() { //check which objects we are over, pick the top one to delete if (CommonInput.GetMouseButtonDown(0)) { var hits = MouseUtils.GetOrderedObjectsUnderMouse(layerMask, go => go.GetComponent <CustomNetTransform>() != null); if (hits.Any()) { if (CustomNetworkManager.IsServer) { Despawn.ServerSingle(hits.First().GetComponentInParent <CustomNetTransform>().gameObject); } else { DevDestroyMessage.Send(hits.First().GetComponentInParent <CustomNetTransform>().gameObject, ServerData.UserID, PlayerList.Instance.AdminToken); } } } }
public void ServerPerformInteraction(HandApply interaction) { var localPosInt = MatrixManager.Instance.WorldToLocalInt(registerObject.WorldPositionServer, registerObject.Matrix); var OreItems = registerObject.Matrix.Get <ItemAttributesV2>(localPosInt + Vector3Int.up, true); foreach (var Ore in OreItems) { foreach (var exOre in expectedOres) { if (Ore != null) { if (Ore.HasTrait(exOre.Trait)) { var inStackable = Ore.gameObject.GetComponent <Stackable>(); Spawn.ServerPrefab(exOre.Material, registerObject.WorldPositionServer + Vector3Int.down, transform.parent, count: inStackable.Amount); Despawn.ServerSingle(Ore.transform.gameObject); } } } } }
public void ServerPerformInteraction(PositionalHandApply interaction) { // wirecutters can be used to cut this cable Vector3Int worldPosInt = interaction.WorldPositionTarget.To2Int().To3Int(); var matrixInfo = MatrixManager.AtPoint(worldPosInt, true); var localPosInt = MatrixManager.WorldToLocalInt(worldPosInt, matrixInfo); var matrix = matrixInfo?.Matrix; if (matrix == null || matrix.IsClearUnderfloorConstruction(localPosInt, true) == false) { return; } ToolUtils.ServerPlayToolSound(interaction); Spawn.ServerPrefab( machineConnectorPrefab, gameObject.AssumedWorldPosServer(), // Random positioning to make it clear this is disassembled scatterRadius: 0.35f, localRotation: RandomUtils.RandomRotation2D()); _ = Despawn.ServerSingle(gameObject); }
/// <summary> /// Decrease ammo count by given number. /// </summary> /// <returns></returns> public virtual void ExpendAmmo(int amount = 1) { if (ClientAmmoRemains < amount) { Logger.LogWarning("Client ammo count is too low, cannot expend that much ammo. Make sure" + " to check ammo count before expending it.", Category.Firearms); } else { clientAmmoRemains -= amount; } if (isServer) { if (ServerAmmoRemains < amount) { Logger.LogWarning("Server ammo count is too low, cannot expend that much ammo. Make sure" + " to check ammo count before expending it.", Category.Firearms); } else { SyncServerAmmo(serverAmmoRemains, serverAmmoRemains - amount); if (!isClip) { for (int i = amount; i != 0; i--) { containedBullets.RemoveAt(0); //remove shot projectile containedProjectilesFired.RemoveAt(0); UpdateProjectile(); //sets the projectile that will be fired next } } if (isClip && serverAmmoRemains == 0) { Despawn.ServerSingle(gameObject); } } } Logger.LogTraceFormat("Expended {0} shots, now serverAmmo {1} clientAmmo {2}", Category.Firearms, amount, serverAmmoRemains, clientAmmoRemains); }
public void Explode() { if (hasExploded) { return; } hasExploded = true; if (!CustomNetworkManager.IsServer) { return; } if (lemonPotencyOverride == 0) { lemonPotency = grownFood.GetPlantData().Potency; } else { lemonPotency = lemonPotencyOverride; } finalDamage = maxDamage * (lemonPotency / 100f); finalRadius = Convert.ToSingle(Math.Ceiling(maxRadius * (lemonPotency / 100f))); // Get data from grenade before despawning var explosionMatrix = registerItem.Matrix; var worldPos = objectBehaviour.AssumedWorldPositionServer(); // Despawn grenade Despawn.ServerSingle(gameObject); // Explosion here var explosionGO = Instantiate(explosionPrefab, explosionMatrix.transform); explosionGO.transform.position = worldPos; explosionGO.SetExplosionData(Mathf.RoundToInt(finalDamage), finalRadius); explosionGO.Explode(explosionMatrix); }
public void ServerPerformInteraction(HandApply interaction) { if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Wrench)) { //deconsruct ToolUtils.ServerUseToolWithActionMessages(interaction, 2f, "You start deconstructing the conveyor belt...", $"{interaction.Performer.ExpensiveName()} starts deconstructing the conveyor belt...", "You deconstruct the conveyor belt.", $"{interaction.Performer.ExpensiveName()} deconstructs the conveyor belt.", () => { Spawn.ServerPrefab(CommonPrefabs.Instance.Metal, SpawnDestination.At(gameObject), 5); Despawn.ServerSingle(gameObject); }); } else if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Screwdriver)) //change direction { int count = (int)CurrentDirection + 1; if (count > 11) { count = 0; } ToolUtils.ServerUseToolWithActionMessages(interaction, 1f, "You start redirecting the conveyor belt...", $"{interaction.Performer.ExpensiveName()} starts redirecting the conveyor belt...", "You redirect the conveyor belt.", $"{interaction.Performer.ExpensiveName()} redirects the conveyor belt.", () => { CurrentDirection = (ConveyorDirection)count; spriteHandler.ChangeSpriteVariant(count); }); } }
private void Burn() { var worldPos = gameObject.AssumedWorldPosServer(); var tr = gameObject.transform.parent; var rotation = RandomUtils.RandomRotation2D(); // Print burn out message if in players inventory if (pickupable && pickupable.ItemSlot != null) { var player = pickupable.ItemSlot.Player; if (player) { Chat.AddExamineMsgFromServer(player.gameObject, $"Your {gameObject.ExpensiveName()} goes out."); } } // Despawn cigarette Despawn.ServerSingle(gameObject); // Spawn cigarette butt Spawn.ServerPrefab(buttPrefab, worldPos, tr, rotation); }
private void RollRandomPool(MatrixInfo matrixInfo) { for (int i = 0; i < lootCount; i++) { PoolData pool = null; // Roll attempt is a safe check in the case the mapper did tables with low % and we can't find // anything to spawn in 5 attempts int rollAttempt = 0; while (pool == null) { if (rollAttempt >= MaxAmountRolls) { break; } var tryPool = poolList.PickRandom(); if (DMMath.Prob(tryPool.Probability)) { pool = tryPool; } else { rollAttempt++; } } if (pool == null) { // didn't spawned anything - just destroy spawner _ = Despawn.ServerSingle(gameObject); return; } SpawnItems(pool); } _ = Despawn.ServerSingle(gameObject); }
private void CheckCooked() { if (storedCookable == null) { return; } // True if the item's total cooking time exceeds the item's minimum cooking time. if (storedCookable.AddCookingTime(Time.deltaTime)) { // Swap item for its cooked version, if applicable. if (storedCookable.CookedProduct == null) { return; } Despawn.ServerSingle(storedCookable.gameObject); GameObject cookedItem = Spawn.ServerPrefab(storedCookable.CookedProduct).GameObject; Inventory.ServerAdd(cookedItem, storageSlot); } }
private void CheckCooked(float cookTime) { var itemsOnGrill = Matrix.Get <ObjectBehaviour>(registerTile.LocalPositionServer, ObjectType.Item, true) .Where(ob => ob != null && ob.gameObject != gameObject); foreach (var onGrill in itemsOnGrill) { if (onGrill.gameObject.TryGetComponent(out Cookable slotCooked) && slotCooked.CookableBy.HasFlag(CookSource.Griddle)) { if (slotCooked.AddCookingTime(cookTime) == true) { // Swap item for its cooked version, if applicable. if (slotCooked.CookedProduct == null) { return; } Spawn.ServerPrefab(slotCooked.CookedProduct, slotCooked.gameObject.transform.position, transform.parent); _ = Despawn.ServerSingle(slotCooked.gameObject); } } } }
void ValidateAdmin() { var admin = PlayerList.Instance.GetAdmin(AdminId, AdminToken); if (admin == null) { return; } if (ToDestroy.Equals(NetId.Invalid)) { Logger.LogWarning("Attempted to destroy an object with invalid netID, destroy will not occur.", Category.ItemSpawn); } else { LoadNetworkObject(ToDestroy); Vector2Int worldPos = NetworkObject.transform.position.To2Int(); UIManager.Instance.adminChatWindows.adminToAdminChat.ServerAddChatRecord( $"{admin.ExpensiveName()} destroyed a {NetworkObject} at {worldPos}", AdminId); Despawn.ServerSingle(NetworkObject); } }
private IEnumerator SpawnMobs(List <GameObject> itemSpots) { yield return(WaitFor.Seconds(30)); foreach (var itemSpot in itemSpots) { if (itemSpot.TryGetComponent <RandomItemSpot>(out var spot) == false) { continue; } var tile = spot.GetComponent <RegisterTile>(); if (tile.Matrix.IsPassableAtOneMatrixOneTile(tile.LocalPositionServer, true) == false) { _ = Despawn.ServerSingle(itemSpot); continue; } spot.RollRandomPool(true, true); } }
public void ServerPerformInteraction(HandApply interaction) { var pos = interaction.Performer.WorldPosServer(); var pna = interaction.Performer.GetComponent <PlayerNetworkActions>(); var item = pna.GetActiveHandItem(); if (Validations.HasItemTrait(item, CommonTraits.Instance.Wirecutter)) { SoundManager.PlayNetworkedAtPos("WireCutter", pos, 1f, sourceObj: gameObject); if (posterVariant == Posters.Ripped) { Chat.AddExamineMsgFromServer(interaction.Performer, "You carefully remove the remnants of the poster."); } else { Chat.AddExamineMsgFromServer(interaction.Performer, "You carefully remove the poster from the wall."); rolledPosterPrefab.GetComponent <RolledPoster>().posterVariant = posterVariant; Spawn.ServerPrefab(rolledPosterPrefab, pos, interaction.Performer.transform.parent); } Despawn.ServerSingle(gameObject); return; } if (posterVariant == Posters.Ripped) { return; } Chat.AddLocalMsgToChat(interaction.Performer.ExpensiveName() + " rips the poster in a single, decisive motion!", pos, gameObject); SoundManager.PlayNetworkedAtPos("PosterRipped", pos, sourceObj: gameObject); SyncPosterType(posterVariant, Posters.Ripped); }
/// <summary> /// Turns this body part into ash while protecting items inside of that cannot be ashed. /// </summary> private void AshBodyPart() { if (currentBurnDamageLevel >= TraumaDamageLevel.CRITICAL) { IEnumerable <ItemSlot> internalItemList = OrganStorage.GetItemSlots(); foreach (ItemSlot item in internalItemList) { Integrity itemObject = item.ItemObject.OrNull()?.GetComponent <Integrity>(); if (itemObject != null) //Incase this is an empty slot { if (itemObject.Resistances.FireProof || itemObject.Resistances.Indestructable) { Inventory.ServerDrop(item); } } } _ = Spawn.ServerPrefab(OrganStorage.AshPrefab, HealthMaster.RegisterTile.WorldPosition); HealthMaster.DismemberBodyPart(this); _ = Despawn.ServerSingle(gameObject); } }
/// <summary> /// Stage 4, screw in the airlock electronics to finish, or crowbar the electronics out to go back to stage 3. /// </summary> /// <param name="interaction"></param> private void ElectronicsAddedStateInteraction(HandApply interaction) { if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Screwdriver) && airlockElectronicsSlot.IsOccupied) { //screw in the airlock electronics ToolUtils.ServerUseToolWithActionMessages(interaction, 2f, $"You start to screw the {airlockElectronicsSlot.ItemObject.ExpensiveName()} into place...", $"{interaction.Performer.ExpensiveName()} starts to screw the {airlockElectronicsSlot.ItemObject.ExpensiveName()} into place...", "You finish the airlock.", $"{interaction.Performer.ExpensiveName()} finishes the airlock.", () => { if (glassAdded && airlockWindowedToSpawn) { ServerSpawnAirlock(airlockWindowedToSpawn); } else { ServerSpawnAirlock(airlockToSpawn); } _ = Despawn.ServerSingle(gameObject); }); } else if (Validations.HasUsedItemTrait(interaction, CommonTraits.Instance.Crowbar) && airlockElectronicsSlot.IsOccupied) { //Crowbar the electronics out ToolUtils.ServerUseToolWithActionMessages(interaction, 2f, "You start to remove electronics from the airlock assembly...", $"{interaction.Performer.ExpensiveName()} starts to remove electronics from the airlock assembly...", "You remove the airlock electronics from the airlock assembly.", $"{interaction.Performer.ExpensiveName()} removes the electronics from the airlock assembly.", () => { Inventory.ServerDrop(airlockElectronicsSlot); stateful.ServerChangeState(cablesAddedState); overlayHackingHandler.ChangeSprite((int)Panel.WiresAdded); }); } }
protected override void DoRandomAction() { // Check if we alreday laid all possible egs if (currentLaidEggs >= maxEggsAmount) { return; } // roll current mood level if (DMMath.Prob(mood.LevelPercent) == false) { return; } if (grownChicken) { // Lay egg var eggGo = Spawn.ServerPrefab( egg, gameObject.RegisterTile().WorldPosition, scatterRadius: 1f); if (!eggGo.Successful) { return; } // chances of fertilized egg are equal to mommy's happiness eggGo.GameObject.GetComponent <ChickenEgg>().SetFertilizedChance(mood.LevelPercent); currentLaidEggs++; } else { // grow to become a cute chicken Spawn.ServerPrefab(possibleGrownForms.PickRandom(), gameObject.RegisterTile().WorldPosition); _ = Despawn.ServerSingle(gameObject); } }
private void TryWeld(HandApply interaction) { if (panelOpen == false) { Chat.AddExamineMsgFromServer(interaction.Performer, "Open the cameras back panel first"); return; } if (wiresCut == false) { Chat.AddExamineMsgFromServer(interaction.Performer, "Cut the cameras wires first"); return; } if (Validations.HasUsedActiveWelder(interaction)) { //Unweld from wall ToolUtils.ServerUseToolWithActionMessages(interaction, 2f, "You start unwelding the camera assembly from the wall...", $"{interaction.Performer.ExpensiveName()} starts unwelding the camera assembly from the wall...", "You unweld the camera assembly onto the wall.", $"{interaction.Performer.ExpensiveName()} unwelds the camera assembly from the wall.", () => { var result = Spawn.ServerPrefab(CommonPrefabs.Instance.CameraAssembly, registerObject.WorldPositionServer, transform.parent); if (result.Successful) { result.GameObject.GetComponent <Rotatable>().FaceDirection(GetComponent <Rotatable>().CurrentDirection); result.GameObject.GetComponent <CameraAssembly>().SetState(CameraAssembly.CameraAssemblyState.Unwelded); } _ = Despawn.ServerSingle(gameObject); }); } }
public virtual void TryFeed(HandApply touchSource) { //Hand touched if (touchSource.HandObject == null) { Chat.AddWarningMsgFromServer(touchSource.Performer, emptyHandMessages.PickRandom()); return; } //Check for right itemtrait if (!Validations.HasAnyTrait(touchSource.HandObject, acceptedItems)) { Chat.AddWarningMsgFromServer(touchSource.Performer, wrongItemMessages.PickRandom()); return; } //Is correct item if (despawnItemOnFeed) { if (touchSource.HandObject.TryGetComponent <Stackable>(out var stackable)) { if (!stackable.ServerConsume(howManyToConsume)) { //Not enough items in stack Chat.AddExamineMsgFromServer(touchSource.Performer, "The artifact looks unimpressed"); return; } } else { Despawn.ServerSingle(touchSource.HandObject); } } Chat.AddExamineMsgFromServer(touchSource.Performer, acceptedItemMessages.PickRandom()); StartCoroutine(Timer(touchSource)); }
public void ServerPerformInteraction(PositionalHandApply interaction) { if (Validations.HasItemTrait(interaction.HandObject, CommonTraits.Instance.Wrench)) { ToolUtils.ServerPlayToolSound(interaction); Spawn.ServerPrefab(CommonPrefabs.Instance.Metal, interaction.WorldPositionTarget.RoundToInt(), transform.parent, count: 1, scatterRadius: Spawn.DefaultScatterRadius, cancelIfImpassable: true); Despawn.ServerSingle(gameObject); return; } void ProgressComplete() { Chat.AddExamineMsgFromServer(interaction.Performer, "You assemble a rack."); Spawn.ServerPrefab(rackPrefab, interaction.WorldPositionTarget.RoundToInt(), interaction.Performer.transform.parent); var handObj = interaction.HandObject; if (handObj != null && handObj.GetInstanceID() == gameObject.GetInstanceID()) // the rack parts were assembled from the hands, despawn in inventory-fashion { // (note: instanceIDs used in case somebody starts assembling rack parts on the ground with rack parts in hand (which was not possible at the time this was written)) Inventory.ServerDespawn(interaction.HandSlot); } else // the rack parts were assembled from the ground, despawn in general fashion { Despawn.ServerSingle(gameObject); } } var bar = StandardProgressAction.Create(ProgressConfig, ProgressComplete) .ServerStartProgress(interaction.WorldPositionTarget.RoundToInt(), 5f, interaction.Performer); if (bar != null) { Chat.AddExamineMsgFromServer(interaction.Performer, "You start constructing a rack..."); } }