private void ServerRemote_OnClientInteractFinish(IWorldObject worldObject) { var character = ServerRemoteContext.Character; if (!InteractionCheckerSystem.SharedUnregister(character, worldObject, isAbort: false)) { return; } Logger.Info($"Client {character} informed that the object interaction with {worldObject} is finished"); this.ServerFinishInteractionInternal(character, worldObject); }
private IReadOnlyList <GarageVehicleEntry> ServerRemote_GetVehiclesList() { var character = ServerRemoteContext.Character; var currentInteractionObject = InteractionCheckerSystem.SharedGetCurrentInteraction(character); if (!(currentInteractionObject?.ProtoWorldObject is IProtoVehicleAssemblyBay)) { Logger.Warning(character + " is not interacting with any vehicle assembly bay"); return(Array.Empty <GarageVehicleEntry>()); } var onlyVehiclesInGarage = !PveSystem.ServerIsPvE; return(ServerGetCharacterVehicles(character, onlyVehiclesInGarage)); }
private void SharedAbortActionInternal(ICharacter character, TActionRequest request) { var characterPrivateState = PlayerCharacter.GetPrivateState(character); var state = characterPrivateState.CurrentActionState; if (!(state is TActionState actionState)) { // no action or action of another state type return; } if (!request.Equals(actionState.Request)) { // different request active return; } if (!state.IsCompleted) { // reset action state actionState.Cancel(); // return now - because this method was just called again by SetCurrentActionState return; } // ensure the action state is reset characterPrivateState.SetCurrentActionState(null); Logger.Info("Action cancelled: " + state, character); if (state.TargetWorldObject is not null) { InteractionCheckerSystem.SharedUnregister(character, state.TargetWorldObject, isAbort: false); } if (IsClient && !state.IsCancelledByServer) { Logger.Info("Sending action abort request: " + request); Instance.CallServer(_ => _.ServerRemote_AbortAction(request)); } else if (IsServer && !ServerRemoteContext.IsRemoteCall) { Instance.CallClient(character, _ => _.ClientRemote_AbortAction(request)); // TODO: notify other players as well } }
private bool ServerRemote_PutCurrentVehicle() { if (!PveSystem.ServerIsPvE) { throw new Exception("This feature is available only in PvE"); } var character = ServerRemoteContext.Character; var currentInteractionObject = InteractionCheckerSystem.SharedGetCurrentInteraction(character); if (!(currentInteractionObject?.ProtoWorldObject is IProtoVehicleAssemblyBay protoVehicleAssemblyBay)) { throw new Exception("Player is not interacting with an vehicle assembly bay"); } using var tempVehiclesList = Api.Shared.GetTempList <IDynamicWorldObject>(); protoVehicleAssemblyBay.SharedGetVehiclesOnPlatform( vehicleAssemblyBay: (IStaticWorldObject)currentInteractionObject, tempVehiclesList); var isPutAtLeastOne = false; foreach (var vehicle in tempVehiclesList.AsList()) { if (ServerCanCharacterPutVehicleIntoGarage(vehicle, byCharacter: character)) { ServerPutIntoGarage(vehicle); isPutAtLeastOne = true; } } if (isPutAtLeastOne) { // notify other players in scope var soundPosition = currentInteractionObject.TilePosition.ToVector2D() + protoVehicleAssemblyBay.PlatformCenterWorldOffset; using var tempPlayers = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(currentInteractionObject, tempPlayers); tempPlayers.Remove(character); Instance.CallClient(tempPlayers.AsList(), _ => _.ClientRemote_OnVehiclePutToGarageByOtherPlayer(soundPosition)); } return(isPutAtLeastOne); }
public static async void ClientPutCurrentVehicle() { if (!PveSystem.ClientIsPve(logErrorIfDataIsNotYetAvailable: true)) { // this feature is available only in PvE return; } var vehicleAssemblyBay = InteractionCheckerSystem.SharedGetCurrentInteraction( ClientCurrentCharacterHelper.Character); var isSuccess = await Instance.CallServer(_ => _.ServerRemote_PutCurrentVehicle()); if (isSuccess) { Client.Audio.PlayOneShot(SoundResourcePutVehicle, vehicleAssemblyBay); } }
private bool ServerRemote_OnClientInteractStart(IWorldObject worldObject) { var character = ServerRemoteContext.Character; if (worldObject is null || !worldObject.ProtoWorldObject.SharedCanInteract(character, worldObject, writeToLog: true)) { // player is too far from the world object or world object is destroyed return(false); } InteractionCheckerSystem.SharedAbortCurrentInteraction(character); var proto = SharedGetProto(worldObject); proto.ServerOnClientInteract(character, worldObject); if (proto.IsAutoEnterPrivateScopeOnInteraction) { // enter private scope - containers will be sent to the player character Server.World.EnterPrivateScope(character, worldObject); } // register private scope exit on interaction cancel InteractionCheckerSystem.SharedRegister( character, worldObject, finishAction: isAbort => { if (worldObject.IsDestroyed) { return; } this.ServerFinishInteractionInternal(character, worldObject); if (isAbort) { // notify client this.CallClient(character, _ => this.ClientRemote_FinishInteraction(worldObject)); } }); Logger.Info($"Started object interaction with {worldObject} for {character}"); return(true); }
private void ServerRemote_Repair() { var character = ServerRemoteContext.Character; var tinkerTableObject = InteractionCheckerSystem.SharedGetCurrentInteraction(character) as IStaticWorldObject; this.VerifyGameObject(tinkerTableObject); var worldObjectPrivateState = GetPrivateState(tinkerTableObject); var containerInput = worldObjectPrivateState.ContainerInput; var containerOutput = worldObjectPrivateState.ContainerOutput; var inputItem1 = containerInput.GetItemAtSlot(0); var inputItem2 = containerInput.GetItemAtSlot(1); if (!ValidateCanRepair(character, tinkerTableObject, out var error)) { Logger.Warning(tinkerTableObject + " cannot repair: " + error, character); return; } if (!CreativeModeSystem.SharedIsInCreativeMode(character)) { InputItemsHelper.ServerDestroyItems(character, RequiredRepairComponentItems); } var resultDurabilityFraction = SharedCalculateResultDurabilityFraction(inputItem1, inputItem2, character); Server.Items.DestroyItem(inputItem2); Server.Items.MoveOrSwapItem(inputItem1, containerOutput, out _); var resultItemProto = (IProtoItemWithDurablity)inputItem1.ProtoGameObject; var resultItemPrivateState = inputItem1.GetPrivateState <IItemWithDurabilityPrivateState>(); resultItemPrivateState.DurabilityCurrent = (uint)Math.Round( resultDurabilityFraction * resultItemProto.DurabilityMax, MidpointRounding.AwayFromZero); character.ServerAddSkillExperience <SkillMaintenance>( SkillMaintenance.ExperiencePerItemRepaired); Logger.Info( $"Item repaired: {inputItem1}. Second item was destroyed to use for repair components: {inputItem2}"); }
public void SharedOnActionCompleted(TActionState state) { var character = state.Character; var request = state.Request; Logger.Info("Action completed: " + request, character); if (state.TargetWorldObject is not null) { InteractionCheckerSystem.SharedUnregister(character, state.TargetWorldObject, isAbort: false); } var canComplete = true; try { // ensure the request is still valid this.SharedValidateRequest(request); } catch (Exception ex) { canComplete = false; Logger.Warning("Exception during completed action processing: " + ex.Message + Environment.NewLine + request); } if (canComplete) { try { this.SharedOnActionCompletedInternal(state, character); } catch (Exception ex) { Logger.Exception(ex, "Exception during completed action processing"); } } PlayerCharacter.GetPrivateState(character) .SetCurrentActionState(null); }
/// <summary> /// Init on component enabled. /// </summary> public override void Start(ClientComponent parentComponent) { base.Start(parentComponent); // Check if there an action in progress. if (PrivateState.CurrentActionState != null) { readyForInteraction = false; lastActionState = PrivateState.CurrentActionState; } // Check if we opened loot container before enabling component. var currentInteractionObject = InteractionCheckerSystem.SharedGetCurrentInteraction(CurrentCharacter); if (currentInteractionObject?.ProtoWorldObject is ProtoObjectLootContainer) { readyForInteraction = false; } }
public static async void ClientTakeVehicle(uint vehicleGameObjectId) { var vehicleAssemblyBay = InteractionCheckerSystem.SharedGetCurrentInteraction( ClientCurrentCharacterHelper.Character); var result = await Instance.CallServer(_ => _.ServerRemote_TakeVehicle(vehicleGameObjectId)); if (result == TakeVehicleResult.Success) { Client.Audio.PlayOneShot(SoundResourceTakeVehicle, vehicleAssemblyBay); WindowObjectVehicleAssemblyBay.CloseActiveMenu(); return; } if (result == TakeVehicleResult.BaseUnderRaidblock) { LandClaimSystem.SharedSendNotificationActionForbiddenUnderRaidblock( ClientCurrentCharacterHelper.Character); return; } var error = result.GetDescription(); if (string.IsNullOrEmpty(error)) { return; } var currentInteractionObject = InteractionCheckerSystem.SharedGetCurrentInteraction(ClientCurrentCharacterHelper.Character); if (!(currentInteractionObject?.ProtoWorldObject is IProtoVehicleAssemblyBay protoVehicleAssemblyBay)) { return; } NotificationSystem.ClientShowNotification( Notification_CannotTakeVehicle_Title, error, NotificationColor.Bad, protoVehicleAssemblyBay.Icon); }
private void ServerRemote_SetLightMode(IStaticWorldObject lightObject, ObjectLightMode mode) { var character = ServerRemoteContext.Character; if (!InteractionCheckerSystem.SharedHasInteraction(character, lightObject, requirePrivateScope: true)) { throw new Exception("The player character is not interacting with the light object"); } var privateState = GetPrivateState(lightObject); var publicState = GetPublicState(lightObject); privateState.Mode = mode; Logger.Important($"Light mode changed: {mode}, light: {lightObject}", character); this.ServerUpdateLight(lightObject, privateState, publicState, deltaTime: 0); }
private void ClientRemote_OnContainerOpened(IStaticWorldObject worldObject) { var privateState = GetPrivateState(worldObject); var itemsContainer = privateState.ItemsContainer; var soundClose = Client.UI.GetApplicationResource <SoundUI>("SoundWindowContainerClose"); var menuWindow = WindowContainerExchange.Show( itemsContainer, soundClose: soundClose, isAutoClose: true); var character = Client.Characters.CurrentPlayerCharacter; InteractionCheckerSystem.SharedRegister( character, worldObject, finishAction: _ => menuWindow.CloseWindow()); ClientInteractionUISystem.Register( worldObject, menuWindow, onMenuClosedByClient: () => { InteractionCheckerSystem.SharedUnregister(character, worldObject, isAbort: false); if (!worldObject.IsDestroyed) { this.CallServer(_ => _.ServerRemote_OnClientInteractFinish(worldObject)); } }); Logger.Important("Started object interaction with " + worldObject); ClientCurrentInteractionMenu.RegisterMenuWindow(menuWindow); ClientCurrentInteractionMenu.Open(); }
public static void ServerAddEnergyCharge(IItemsContainer container, double energyAmountToAdd) { if (energyAmountToAdd <= 0 || container == null || container.IsDestroyed) { return; } ITempList <IItem> tempItemsList; if (container.Owner is ICharacter ownerCharacter) { tempItemsList = SharedGetTempListPowerBanksForCharacter(ownerCharacter, onlyEquippedDevices: false); } else { tempItemsList = Api.Shared.GetTempList <IItem>(); SharedFindPowerBanks(container, tempItemsList); if (container.Owner is IWorldObject worldObject) { foreach (var character in InteractionCheckerSystem.SharedEnumerateCurrentInteractionCharacters( worldObject)) { using var tempCharacterPowerBanks = SharedGetTempListPowerBanksForCharacter(character, onlyEquippedDevices: false); tempItemsList.AddRange(tempCharacterPowerBanks.AsList()); } } } ServerAddEnergyInternal(energyAmountToAdd, tempItemsList.AsList()); tempItemsList.Dispose(); }
/// <summary> /// Check if the character's interaction area collides with the world object click area. /// The character also should not be dead. /// </summary> public bool SharedIsInsideCharacterInteractionArea( ICharacter character, TWorldObject worldObject, bool writeToLog, CollisionGroup requiredCollisionGroup = null) { if (worldObject.IsDestroyed) { return(false); } try { this.VerifyGameObject(worldObject); } catch (Exception ex) { if (writeToLog) { Logger.Exception(ex); } else { Logger.Warning(ex.Message + " during " + nameof(SharedIsInsideCharacterInteractionArea)); } return(false); } bool isInsideInteractionArea; if (worldObject.PhysicsBody.HasShapes) { // check that the world object is inside the interaction area of the character using (var objectsInCharacterInteractionArea = InteractionCheckerSystem.SharedGetTempObjectsInCharacterInteractionArea( character, writeToLog, requiredCollisionGroup)) { isInsideInteractionArea = objectsInCharacterInteractionArea?.Any(t => t.PhysicsBody.AssociatedWorldObject == worldObject) ?? false; } } else { // the world object doesn't have physics shapes // check this object tile by tile // ensure at least one tile of this object is inside the character interaction area // ensure there is direct line of sight between player character and this tile var characterInteractionAreaShape = character.PhysicsBody.Shapes.FirstOrDefault( s => s.CollisionGroup == CollisionGroups.CharacterInteractionArea); isInsideInteractionArea = false; foreach (var tileOffset in ((IProtoStaticWorldObject)worldObject.ProtoWorldObject).Layout.TileOffsets) { var penetration = character.PhysicsBody.PhysicsSpace.TestShapeCollidesWithShape( sourceShape: characterInteractionAreaShape, targetShape: new RectangleShape( position: (worldObject.TilePosition + tileOffset).ToVector2D() + (0.01, 0.01), size: (0.98, 0.98), collisionGroup: CollisionGroups.ClickArea), sourceShapeOffset: character.PhysicsBody.Position); if (!penetration.HasValue) { // this tile is not inside the character interaction area continue; } // the tile is inside the character interaction area // check that there is a direct line between the character and the tile isInsideInteractionArea = true; break; } } if (!isInsideInteractionArea) { // the world object is outside the character interaction area if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - outside the interaction area.", character); if (IsClient) { this.ClientOnCannotInteract(worldObject, CoreStrings.Notification_TooFar, isOutOfRange: true); } } return(false); } // check that there are no other objects on the way between them (defined by default layer) var physicsSpace = character.PhysicsBody.PhysicsSpace; var characterCenter = character.Position + character.PhysicsBody.CenterOffset; var worldObjectCenter = worldObject.TilePosition.ToVector2D() + worldObject.PhysicsBody.CenterOffset; var worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside( characterCenter, CollisionGroups.Default, out var isSuccess); if (!isSuccess) { // the physics body seems to not have the default collider, let's check for the click area instead worldObjectPointClosestToCharacter = worldObject.PhysicsBody.ClampPointInside( characterCenter, CollisionGroups.ClickArea, out _); } // local method for testing if there is an obstacle from current to the specified position bool TestHasObstacle(Vector2D toPosition) { using (var obstaclesOnTheWay = physicsSpace.TestLine( characterCenter, toPosition, CollisionGroup.GetDefault(), sendDebugEvent: writeToLog)) { foreach (var test in obstaclesOnTheWay) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile != null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (testWorldObject == character || testWorldObject == worldObject) { // not an obstacle - it's the character or world object itself continue; } if (!this.CommonIsAllowedObjectToInteractThrought(testWorldObject)) { // obstacle object on the way return(true); } } // no obstacles return(false); } } if (character.ProtoCharacter is PlayerCharacterSpectator) { // don't test for obstacles for spectator character return(true); } // let's test by casting rays from character center to: // 0) world object center // 1) world object point closest to the character // 2) combined - take X from center, take Y from closest // 3) combined - take X from closest, take Y from center if (TestHasObstacle(worldObjectCenter) && TestHasObstacle(worldObjectPointClosestToCharacter) && TestHasObstacle((worldObjectCenter.X, worldObjectPointClosestToCharacter.Y)) && TestHasObstacle((worldObjectPointClosestToCharacter.X, worldObjectCenter.Y))) { // has obstacle if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - there are other objects on the way.", character); if (IsClient) { this.ClientOnCannotInteract(worldObject, CoreStrings.Notification_ObstaclesOnTheWay, isOutOfRange: true); } } return(false); } if (character.GetPublicState <ICharacterPublicState>().IsDead) { // character is dead if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - character is dead.", character); } return(false); } return(true); }
public static string ServerSetOwners( IWorldObject worldObject, List <string> newOwners, NetworkSyncList <string> currentOwners, ICharacter byOwner) { var protoObject = (IProtoObjectWithOwnersList)worldObject.ProtoGameObject; if (!protoObject.HasOwnersList) { throw new Exception("This object doesn't support owners list: " + worldObject); } if (!InteractionCheckerSystem.SharedHasInteraction(ServerRemoteContext.Character, worldObject, requirePrivateScope: true)) { throw new Exception("The player character is not interacting with " + worldObject); } if (!currentOwners.Contains(byOwner.Name) && !CreativeModeSystem.SharedIsInCreativeMode(byOwner)) { return(DialogCannotSetOwners_MessageNotOwner); } if (!((IProtoObjectWithOwnersList)worldObject.ProtoGameObject) .SharedCanEditOwners(worldObject, byOwner)) { return(DialogCannotSetOwners_MessageCannotEdit); } currentOwners.GetDiff(newOwners, out var ownersToAdd, out var ownersToRemove); if (ownersToRemove.Count > 0 && currentOwners.Count == ownersToRemove.Count) { return(DialogCannotSetOwners_MessageCannotRemoveLastOwner); } if (ownersToRemove.Contains(byOwner.Name)) { return(DialogCannotSetOwners_MessageCannotRemoveSelf); } if (ownersToAdd.Count == 0 && ownersToRemove.Count == 0) { Logger.Warning( "No need to change the owners - the new owners list is the same as the current owners list: " + worldObject, characterRelated: byOwner); return(null); } foreach (var n in ownersToAdd) { var name = n; var playerToAdd = Api.Server.Characters.GetPlayerCharacter(name); if (playerToAdd == null) { return(string.Format(DialogCannotSetOwners_MessageFormatPlayerNotFound, name)); } // get proper player name name = playerToAdd.Name; if (currentOwners.AddIfNotContains(name)) { Api.Logger.Important($"Added owner: {name}; {worldObject}", characterRelated: byOwner); } } foreach (var name in ownersToRemove) { if (!currentOwners.Remove(name)) { continue; } Api.Logger.Important($"Removed owner: {name}; {worldObject}", characterRelated: byOwner); var removedPlayer = Api.Server.Characters.GetPlayerCharacter(name); if (removedPlayer == null) { continue; } InteractableWorldObjectHelper.ServerTryAbortInteraction(removedPlayer, worldObject); } ServerInvokeOwnersChangedEvent(worldObject); return(null); }
protected override void CheckInteractionQueue() { if (openedLootContainer != null) { if (InteractionCheckerSystem.SharedHasInteraction(CurrentCharacter, openedLootContainer, true)) { // We get container private state, now take all items from container. var q = openedLootContainer.GetPrivateState <LootContainerPrivateState>(); var result = CurrentCharacter.ProtoCharacter.ClientTryTakeAllItems(CurrentCharacter, q.ItemsContainer, true); if (result.MovedItems.Count > 0) { NotificationSystem.ClientShowItemsNotification( itemsChangedCount: result.MovedItems .GroupBy(p => p.Key.ProtoItem) .ToDictionary(p => p.Key, p => p.Sum(v => v.Value))); } InteractionCheckerSystem.CancelCurrentInteraction(CurrentCharacter); } else if (openedLootContainer.ProtoWorldObject .SharedCanInteract(CurrentCharacter, openedLootContainer, false)) { // Waiting for container private state from server. return; } openedLootContainer = null; readyForInteraction = true; } if (!readyForInteraction) { return; } // Remove from queue while it have object and they in our whitelist if: // - object is destroyed // - if object is container that we already have looted // - if object not IProtoObjectGatherable // - if we can not interact with object right now // - if we can not gather anything from object while (interactionQueue.Count != 0 && EnabledEntityList.Contains(interactionQueue[0].ProtoGameObject) && (interactionQueue[0].IsDestroyed || (lastActionState != null && lastActionState.TargetWorldObject == interactionQueue[0] && lastActionState.IsCompleted && !lastActionState.IsCancelled && !lastActionState.IsCancelledByServer) || !(interactionQueue[0].ProtoGameObject is IProtoObjectGatherable protoGatherable) || !protoGatherable.SharedCanInteract(CurrentCharacter, interactionQueue[0], false) || !protoGatherable.SharedIsCanGather(interactionQueue[0]))) { interactionQueue.RemoveAt(0); } if (interactionQueue.Count == 0) { return; } var request = new WorldActionRequest(CurrentCharacter, interactionQueue[0]); GatheringSystem.Instance.SharedStartAction(request); }
private void ServerRemote_Uninstall(byte slotId) { var character = ServerRemoteContext.Character; var worldObject = InteractionCheckerSystem.GetCurrentInteraction(character); this.VerifyGameObject((IStaticWorldObject)worldObject); var itemToUninstall = character.SharedGetPlayerContainerEquipment() .GetItemAtSlot(slotId); if (itemToUninstall == null) { throw new Exception("No implant installed"); } var itemToUninstallProto = (IProtoItemEquipmentImplant)itemToUninstall.ProtoItem; var biomaterialRequiredAmount = CreativeModeSystem.SharedIsInCreativeMode(character) ? (ushort)0 : itemToUninstallProto.BiomaterialAmountRequiredToUninstall; if (!character.ContainsItemsOfType(ProtoItemBiomaterialVial.Value, requiredCount: biomaterialRequiredAmount) && !CreativeModeSystem.SharedIsInCreativeMode(character)) { throw new Exception("Not enough biomaterial vials"); } // move to inventory if (!Server.Items.MoveOrSwapItem(itemToUninstall, character.SharedGetPlayerContainerInventory(), out _)) { NotificationSystem.ServerSendNotificationNoSpaceInInventory(character); return; } if (biomaterialRequiredAmount > 0) { // destroy vials Server.Items.DestroyItemsOfType(character, ProtoItemBiomaterialVial.Value, countToDestroy: biomaterialRequiredAmount, out _); NotificationSystem.ServerSendItemsNotification( character, ProtoItemBiomaterialVial.Value, -biomaterialRequiredAmount); } if (itemToUninstallProto is ItemImplantBroken) { // broken implant destroys on uninstall Server.Items.DestroyItem(itemToUninstall); NotificationSystem.ServerSendItemsNotification( character, itemToUninstallProto, -1); } Logger.Info("Implant uninstalled: " + itemToUninstall); }
private void ServerRemote_Install(IItem itemToInstall, byte slotId) { var character = ServerRemoteContext.Character; var worldObject = InteractionCheckerSystem.GetCurrentInteraction(character); this.VerifyGameObject((IStaticWorldObject)worldObject); var containerEquipment = character.SharedGetPlayerContainerEquipment(); var currentInstalledItem = containerEquipment.GetItemAtSlot(slotId); if (currentInstalledItem != null) { if (currentInstalledItem == itemToInstall) { Logger.Info("The implant is already installed"); return; } throw new Exception("Please uninstall installed implant"); } if (itemToInstall.Container.OwnerAsCharacter != character || itemToInstall.Container == containerEquipment) { throw new Exception("The item to install must be in character containers (except equipment)"); } if (!containerEquipment.ProtoItemsContainer.CanAddItem( new CanAddItemContext(containerEquipment, itemToInstall, slotId, byCharacter: null, isExploratoryCheck: false))) { throw new Exception("Cannot install implant item there"); } var itemToInstallProto = (IProtoItemEquipmentImplant)itemToInstall.ProtoItem; if (itemToInstallProto is ItemImplantBroken) { throw new Exception("Cannot install broken implant"); } foreach (var equippedItem in containerEquipment.Items) { if (equippedItem.ProtoItem == itemToInstallProto) { throw new Exception("Another implant of this type is already installed: " + itemToInstallProto); } } var biomaterialRequiredAmount = CreativeModeSystem.SharedIsInCreativeMode(character) ? (ushort)0 : itemToInstallProto.BiomaterialAmountRequiredToInstall; if (!character.ContainsItemsOfType(ProtoItemBiomaterialVial.Value, requiredCount: biomaterialRequiredAmount)) { throw new Exception("Not enough biomaterial vials"); } if (!Server.Items.MoveOrSwapItem(itemToInstall, containerEquipment, out _, slotId: slotId)) { throw new Exception("Unknown error - cannot move implant item to the player equipment"); } if (biomaterialRequiredAmount > 0) { // destroy vials Server.Items.DestroyItemsOfType(character, ProtoItemBiomaterialVial.Value, countToDestroy: biomaterialRequiredAmount, out _); NotificationSystem.ServerSendItemsNotification( character, ProtoItemBiomaterialVial.Value, -biomaterialRequiredAmount); } Logger.Info("Implant installed: " + itemToInstall); }
public bool SharedStartAction(TActionRequest request) { if (request == null) { return(false); } var character = request.Character; var characterPrivateState = PlayerCharacter.GetPrivateState(character); if (characterPrivateState.CurrentActionState is TActionState existingState && this.SharedIsSameAction(existingState, request)) { // the same action is already in process Logger.Info( $"Action cannot be started: {request} - already performing the same action", character); return(false); } try { this.SharedValidateRequest(request); } catch (Exception ex) { Logger.Info( $"Action cannot be started: {request} the request is not valid: {Environment.NewLine}{ex.Message}", character); return(false); } var state = this.SharedTryCreateState(request); if (state == null) { Logger.Info( "Action cannot be started: " + request, character); return(false); } state.Request = request; InteractionCheckerSystem.CancelCurrentInteraction(character); characterPrivateState.SetCurrentActionState(state); Logger.Info("Action started: " + request, character); if (IsClient) { this.ClientOnStartActionCompleted(request, state); } if (state.TargetWorldObject != null) { InteractionCheckerSystem.Register( character, state.TargetWorldObject, finishAction: isAbort => { if (!isAbort) { return; } Logger.Warning( $"InteractionCheckerSystem interaction check failed - cancelling \"{this.Name}\" action: {state}", character); this.SharedAbortActionInternal(character, state.Request); }); } return(true); }
private TakeVehicleResult ServerRemote_TakeVehicle(uint vehicleGameObjectId) { var character = ServerRemoteContext.Character; var currentInteractionObject = InteractionCheckerSystem.SharedGetCurrentInteraction(character); if (!(currentInteractionObject?.ProtoWorldObject is IProtoVehicleAssemblyBay protoVehicleAssemblyBay)) { Logger.Warning("Player is not interacting with an vehicle assembly bay", character); return(TakeVehicleResult.Unknown); } var vehicleAssemblyBay = (IStaticWorldObject)currentInteractionObject; var vehicle = Server.World.GetGameObjectById <IDynamicWorldObject>(GameObjectType.DynamicObject, vehicleGameObjectId); if (vehicle == null) { Logger.Warning("Vehicle is not found", character); return(TakeVehicleResult.Unknown); } if (!WorldObjectOwnersSystem.SharedIsOwner(character, vehicle)) { Logger.Warning("Not an owner of the vehicle: " + vehicle, character); return(TakeVehicleResult.NotOwner); } var status = ServerGetVehicleStatus(vehicle, forCharacter: character); switch (status) { case VehicleStatus.InGarage: // allow to take break; case VehicleStatus.InWorld: if (!PveSystem.ServerIsPvE) { Logger.Warning("Cannot take a vehicle from world on a PvP server", character); return(TakeVehicleResult.Unknown); } // allow to take a vehicle from world in PvE only break; case VehicleStatus.InUse: return(TakeVehicleResult.Error_InUse); case VehicleStatus.Docked: return(TakeVehicleResult.Error_Docked); default: return(TakeVehicleResult.Unknown); } if (IsServer && LandClaimSystem.SharedIsUnderRaidBlock(character, vehicleAssemblyBay)) { return(TakeVehicleResult.BaseUnderRaidblock); } if (protoVehicleAssemblyBay.SharedIsBaySpaceBlocked( vehicleAssemblyBay: vehicleAssemblyBay)) { return(TakeVehicleResult.SpaceBlocked); } var position = currentInteractionObject.TilePosition.ToVector2D() + protoVehicleAssemblyBay.PlatformCenterWorldOffset; var vehiclePrivateState = vehicle.GetPrivateState <VehiclePrivateState>(); vehiclePrivateState.IsInGarage = false; vehiclePrivateState.ServerTimeSincePilotOffline = 0; vehiclePrivateState.ServerTimeSinceLastUse = ThresholdNoPilotSeconds + 1; Server.World.SetPosition(vehicle, position, writeToLog: false); vehicle.ProtoWorldObject.SharedCreatePhysics(vehicle); Logger.Important("Vehicle taken out of the garage: " + vehicle, character); // notify other players in scope var soundPosition = currentInteractionObject.TilePosition.ToVector2D() + protoVehicleAssemblyBay.PlatformCenterWorldOffset; using var tempPlayers = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(currentInteractionObject, tempPlayers); tempPlayers.Remove(character); Instance.CallClient(tempPlayers.AsList(), _ => _.ClientRemote_OnVehicleTakenFromGarageByOtherPlayer(soundPosition)); return(TakeVehicleResult.Success); }
public ObjectLandClaimCanUpgradeCheckResult SharedCanUpgrade( IStaticWorldObject worldObjectLandClaim, IProtoObjectLandClaim protoUpgradedLandClaim, ICharacter character, out IConstructionUpgradeEntryReadOnly upgradeEntry, bool writeErrors = true) { if (!this.SharedCanInteract(character, worldObjectLandClaim, writeToLog: writeErrors)) { upgradeEntry = null; return(ObjectLandClaimCanUpgradeCheckResult.ErrorUnknown); } upgradeEntry = null; foreach (var entry in this.ConfigUpgrade.Entries) { if (entry.ProtoStructure == protoUpgradedLandClaim) { upgradeEntry = entry; break; } } var currentLandClaimArea = GetPublicState(worldObjectLandClaim).LandClaimAreaObject; var founderName = LandClaimArea.GetPrivateState(currentLandClaimArea).LandClaimFounder; var result = ObjectLandClaimCanUpgradeCheckResult.Success; if (upgradeEntry == null) { result = ObjectLandClaimCanUpgradeCheckResult.ErrorUnknown; } if (result == ObjectLandClaimCanUpgradeCheckResult.Success) { if (character.Name != founderName && !CreativeModeSystem.SharedIsInCreativeMode(character)) { result = ObjectLandClaimCanUpgradeCheckResult.ErrorNotFounder; } } if (result == ObjectLandClaimCanUpgradeCheckResult.Success) { // validate player know the tech, have enough items, etc if (!upgradeEntry.CheckRequirementsSatisfied(character)) { upgradeEntry = null; result = ObjectLandClaimCanUpgradeCheckResult.ErrorRequirementsNotSatisfied; } } if (result == ObjectLandClaimCanUpgradeCheckResult.Success) { // check there will be no intersection with other areas var landClaimCenterTilePosition = LandClaimSystem.SharedCalculateLandClaimObjectCenterTilePosition(worldObjectLandClaim); if (!LandClaimSystem.SharedCheckCanPlaceOrUpgradeLandClaimThere( protoUpgradedLandClaim, landClaimCenterTilePosition, character)) { result = ObjectLandClaimCanUpgradeCheckResult.ErrorAreaIntersection; } } if (result == ObjectLandClaimCanUpgradeCheckResult.Success) { if (!InteractionCheckerSystem.SharedHasInteraction(character, worldObjectLandClaim, requirePrivateScope: true)) { result = ObjectLandClaimCanUpgradeCheckResult.ErrorNoActiveInteraction; } } if (result == ObjectLandClaimCanUpgradeCheckResult.Success) { if (LandClaimSystem.SharedIsUnderRaidBlock(character, worldObjectLandClaim)) { // the building is in an area under the raid LandClaimSystem.SharedSendNotificationActionForbiddenUnderRaidblock(character); result = ObjectLandClaimCanUpgradeCheckResult.ErrorUnderRaid; } } if (writeErrors && result != ObjectLandClaimCanUpgradeCheckResult.Success) { Logger.Warning( $"Can\'t upgrade: {worldObjectLandClaim} to {protoUpgradedLandClaim}: error code - {result}", character); } return(result); }
private void ServerRemote_Repair() { var character = ServerRemoteContext.Character; var tinkerTableObject = InteractionCheckerSystem.SharedGetCurrentInteraction(character) as IStaticWorldObject; this.VerifyGameObject(tinkerTableObject); var worldObjectPrivateState = GetPrivateState(tinkerTableObject); var containerInput = worldObjectPrivateState.ContainerInput; var containerOutput = worldObjectPrivateState.ContainerOutput; var inputItem1 = containerInput.GetItemAtSlot(0); var inputItem2 = containerInput.GetItemAtSlot(1); if (!ValidateCanRepair(character, tinkerTableObject, out var error)) { Logger.Warning(tinkerTableObject + " cannot repair: " + error, character); return; } if (!CreativeModeSystem.SharedIsInCreativeMode(character)) { InputItemsHelper.ServerDestroyItems(character, RequiredRepairComponentItems); } var resultItemProto = (IProtoItemWithDurability)inputItem1.ProtoGameObject; var resultDurabilityFraction = SharedCalculateResultDurabilityFraction(inputItem1, inputItem2, character); // break the second input item (it will force spawning ammo in this slot if it's a loaded weapon) ItemDurabilitySystem.ServerModifyDurability(inputItem2, -(double)resultItemProto.DurabilityMax, roundUp: false); if (!inputItem2.IsDestroyed) { // ensure the second input item is destroyed Server.Items.DestroyItem(inputItem2); } Server.Items.MoveOrSwapItem(inputItem1, containerOutput, out _); var resultItemPrivateState = inputItem1.GetPrivateState <IItemWithDurabilityPrivateState>(); if (resultDurabilityFraction < 1 && character.SharedHasSkillFlag(SkillMaintenance.Flags.ChanceToRepairCompletely) && RandomHelper.RollWithProbability(0.05)) { // 5% chance to repair item completely rolled successfully resultDurabilityFraction = 1; this.CallClient(character, _ => _.ClientRemote_ItemRepairedCompletely(inputItem1.ProtoItem)); } resultItemPrivateState.DurabilityCurrent = (uint)Math.Round( resultDurabilityFraction * resultItemProto.DurabilityMax, MidpointRounding.AwayFromZero); character.ServerAddSkillExperience <SkillMaintenance>( SkillMaintenance.ExperiencePerItemRepaired); Logger.Info( $"Item repaired: {inputItem1}. Second item was destroyed to use for repair components: {inputItem2}"); }
public static void ServerTryAbortInteraction(ICharacter character, IWorldObject worldObject) { InteractionCheckerSystem.SharedUnregister(character, worldObject, isAbort: true); }
private async void ClientInteractStartAsync(IStaticWorldObject worldObject) { if (this.isAwaitingServerInteraction) { return; } var character = Client.Characters.CurrentPlayerCharacter; if (InteractionCheckerSystem.GetCurrentInteraction(character) == worldObject) { // already interacting with this object return; } this.isAwaitingServerInteraction = true; try { var requestId = ++lastRequestId; var isOpened = await this.CallServer(_ => _.ServerRemote_OnClientInteractStart(worldObject)); if (!isOpened || requestId != lastRequestId) { return; } } finally { this.isAwaitingServerInteraction = false; } var menuWindow = SharedGetProto(worldObject).ClientOpenUI(worldObject); if (menuWindow == null) { Logger.Important("Cannot open menu for object interaction with " + worldObject); this.CallServer(_ => _.ServerRemote_OnClientInteractFinish(worldObject)); return; } ClientCurrentInteractionMenu.RegisterMenuWindow(menuWindow); InteractionCheckerSystem.Register( character, worldObject, finishAction: _ => menuWindow.CloseWindow()); ClientInteractionUISystem.Register( worldObject, menuWindow, onMenuClosedByClient: () => { InteractionCheckerSystem.Unregister(character, worldObject, isAbort: false); if (!worldObject.IsDestroyed) { ++lastRequestId; this.CallServer(_ => _.ServerRemote_OnClientInteractFinish(worldObject)); } }); Logger.Important("Started object interaction with " + worldObject); ClientCurrentInteractionMenu.Open(); }
private async void ClientInteractStartAsync(IWorldObject worldObject) { if (this.isAwaitingServerInteraction) { return; } var character = Client.Characters.CurrentPlayerCharacter; if (InteractionCheckerSystem.SharedGetCurrentInteraction(character) == worldObject) { // already interacting with this object return; } this.isAwaitingServerInteraction = true; try { var requestId = ++lastRequestId; var isOpened = await this.CallServer(_ => _.ServerRemote_OnClientInteractStart(worldObject)); if (!isOpened || requestId != lastRequestId) { return; } } finally { this.isAwaitingServerInteraction = false; } var objectWindow = SharedGetProto(worldObject).ClientOpenUI(worldObject); if (objectWindow is null) { Logger.Info("Cannot open menu for object interaction with " + worldObject); this.CallServer(_ => _.ServerRemote_OnClientInteractFinish(worldObject)); return; } Api.SafeInvoke(() => ClientMenuCreated?.Invoke(worldObject, objectWindow)); if (!(objectWindow is IMenu)) { ClientCurrentInteractionMenu.RegisterMenuWindow(objectWindow); } else { ClientCurrentInteractionMenu.TryCloseCurrentMenu(); } InteractionCheckerSystem.SharedRegister( character, worldObject, finishAction: _ => objectWindow.CloseWindow()); ClientInteractionUISystem.Register( worldObject, objectWindow, onMenuClosedByClient: () => { InteractionCheckerSystem.SharedUnregister(character, worldObject, isAbort: false); if (!worldObject.IsDestroyed) { ++lastRequestId; this.CallServer(_ => _.ServerRemote_OnClientInteractFinish(worldObject)); } }); Logger.Info("Started object interaction with " + worldObject); if (objectWindow is IMenu objectMenu) { if (!objectMenu.IsOpened) { objectMenu.Toggle(); } } else { ClientCurrentInteractionMenu.Open(); } }
/// <summary> /// Check if the character's interaction area collides with the world object click area. /// The character also should not be dead. /// </summary> public virtual bool SharedIsInsideCharacterInteractionArea( ICharacter character, TWorldObject worldObject, bool writeToLog, CollisionGroup requiredCollisionGroup = null) { if (worldObject.IsDestroyed) { return(false); } try { this.VerifyGameObject(worldObject); } catch (Exception ex) { if (writeToLog) { Logger.Exception(ex); } else { Logger.Warning(ex.Message + " during " + nameof(SharedIsInsideCharacterInteractionArea)); } return(false); } if (character.GetPublicState <ICharacterPublicState>().IsDead || IsServer && !character.ServerIsOnline) { if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - character is dead or offline.", character); } return(false); } bool isInsideInteractionArea; if (worldObject.PhysicsBody.HasShapes && worldObject.PhysicsBody.HasAnyShapeCollidingWithGroup(CollisionGroups.ClickArea)) { // check that the world object is inside the interaction area of the character using var objectsInCharacterInteractionArea = InteractionCheckerSystem.SharedGetTempObjectsInCharacterInteractionArea( character, writeToLog, requiredCollisionGroup); isInsideInteractionArea = false; if (objectsInCharacterInteractionArea is not null) { foreach (var t in objectsInCharacterInteractionArea.AsList()) { if (!ReferenceEquals(worldObject, t.PhysicsBody.AssociatedWorldObject)) { continue; } isInsideInteractionArea = true; break; } } } else if (worldObject.ProtoWorldObject is IProtoStaticWorldObject protoStaticWorldObject) { // the world object doesn't have click area collision shapes // check this object tile by tile // ensure at least one tile of this object is inside the character interaction area // ensure there is direct line of sight between player character and this tile var characterInteractionAreaShape = character.PhysicsBody.Shapes.FirstOrDefault( s => s.CollisionGroup == CollisionGroups.CharacterInteractionArea); isInsideInteractionArea = false; foreach (var tileOffset in protoStaticWorldObject.Layout.TileOffsets) { var penetration = character.PhysicsBody.PhysicsSpace.TestShapeCollidesWithShape( sourceShape: characterInteractionAreaShape, targetShape: new RectangleShape( position: (worldObject.TilePosition + tileOffset).ToVector2D() + (0.01, 0.01), size: (0.98, 0.98), collisionGroup: CollisionGroups.ClickArea), sourceShapeOffset: character.PhysicsBody.Position); if (!penetration.HasValue) { // this tile is not inside the character interaction area continue; } // the tile is inside the character interaction area // check that there is a direct line between the character and the tile isInsideInteractionArea = true; break; } } else { isInsideInteractionArea = false; } if (!isInsideInteractionArea) { // the world object is outside the character interaction area if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - outside the interaction area.", character); if (IsClient) { ClientOnCannotInteract(worldObject, CoreStrings.Notification_TooFar, isOutOfRange: true); } } return(false); } if (character.ProtoCharacter is PlayerCharacterSpectator) { // don't test for obstacles for spectator character return(true); } // check that there are no other objects on the way between them (defined by default layer) var physicsSpace = character.PhysicsBody.PhysicsSpace; var characterCenter = character.Position + character.PhysicsBody.CenterOffset; if (!ObstacleTestHelper.SharedHasObstaclesOnTheWay(characterCenter, physicsSpace, worldObject, sendDebugEvents: writeToLog)) { return(true); } if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - there are other objects on the way.", character); if (IsClient) { ClientOnCannotInteract(worldObject, CoreStrings.Notification_ObstaclesOnTheWay, isOutOfRange: true); } } return(false); }
public bool ServerGather(IStaticWorldObject worldObject, ICharacter character) { var privateState = GetPrivateState(worldObject); if (privateState.IsDropListSpawned) { // this loot container was already search - drop list was already spawned return(true); } // spawn items accordingly to the droplist privateState.IsDropListSpawned = true; var lootDroplist = this.ServerGetLootDroplist(worldObject); var dropItemContext = new DropItemContext(character, worldObject); CreateItemResult dropItemResult; var attemptRemains = 100; var itemsContainer = privateState.ItemsContainer; do { dropItemResult = lootDroplist.TryDropToContainer(itemsContainer, dropItemContext); } // ensure that at least something is spawned... // perhaps that's not a good idea, but we have an attempts limit while (dropItemResult.TotalCreatedCount == 0 && --attemptRemains > 0); Server.Items.SetSlotsCount(itemsContainer, (byte)itemsContainer.OccupiedSlotsCount); character.ServerAddSkillExperience <SkillSearching>(SkillSearching.ExperienceAddWhenSearching); Server.World.EnterPrivateScope(character, worldObject); // register private scope exit on interaction cancel InteractionCheckerSystem.SharedRegister( character, worldObject, finishAction: isAbort => { if (worldObject.IsDestroyed) { return; } Server.World.ExitPrivateScope(character, worldObject); if (isAbort) { // notify client this.CallClient(character, _ => _.ClientRemote_FinishInteraction(worldObject)); } if (this.IsAutoDestroyWhenLooted) { // container was closed - destroy it Server.World.DestroyObject(worldObject); } }); Logger.Important($"Started object interaction with {worldObject} for {character}"); this.CallClient(character, _ => _.ClientRemote_OnContainerOpened(worldObject)); return(true); }
public bool ServerGather(IStaticWorldObject worldObject, ICharacter character) { var privateState = GetPrivateState(worldObject); if (privateState.IsDropListSpawned) { // this loot container was already search - drop list was already spawned return(true); } // spawn items accordingly to the droplist privateState.IsDropListSpawned = true; var skillExperienceToAdd = SkillSearching.ExperienceAddWhenSearching * this.SearchingSkillExperienceMultiplier; var lootDroplist = this.ServerGetLootDroplist(worldObject); var dropItemContext = new DropItemContext(character, worldObject); CreateItemResult dropItemResult; if (this.IsAutoTakeAll) { // try to simply pickup the content dropItemResult = lootDroplist.TryDropToCharacter(character, dropItemContext, sendNoFreeSpaceNotification: false); if (dropItemResult.IsEverythingCreated && dropItemResult.TotalCreatedCount > 0) { NotificationSystem.ServerSendItemsNotification(character, dropItemResult); Server.World.DestroyObject(worldObject); // destroy object after success pickup character.ServerAddSkillExperience <SkillSearching>(skillExperienceToAdd); ServerLootEventHelper.OnLootReceived(character, worldObject); return(true); } dropItemResult.Rollback(); } // create a container and drop items there var attemptRemains = 100; var itemsContainer = privateState.ItemsContainer; do { dropItemResult = lootDroplist.TryDropToContainer(itemsContainer, dropItemContext); } // ensure that at least something is spawned... // perhaps that's not a good idea, but we have an attempts limit while (dropItemResult.TotalCreatedCount == 0 && --attemptRemains > 0); Server.Items.SetSlotsCount(itemsContainer, itemsContainer.OccupiedSlotsCount); character.ServerAddSkillExperience <SkillSearching>(skillExperienceToAdd); ServerLootEventHelper.OnLootReceived(character, worldObject); Server.World.EnterPrivateScope(character, worldObject); // register private scope exit on interaction cancel InteractionCheckerSystem.SharedRegister( character, worldObject, finishAction: isAbort => { if (worldObject.IsDestroyed) { return; } Server.World.ExitPrivateScope(character, worldObject); if (isAbort) { // notify client this.CallClient(character, _ => _.ClientRemote_FinishInteraction(worldObject)); } if (this.IsAutoDestroyWhenLooted) { // container was closed - destroy it Server.World.DestroyObject(worldObject); } }); Logger.Important($"Started object interaction with {worldObject} for {character}"); this.CallClient(character, _ => _.ClientRemote_OnContainerOpened(worldObject)); return(true); }