public bool ClientCanStartRefill(IItem item) { var publicState = GetPublicState(item); if (!publicState.IsActive) { return(true); } // light is active - try deactivate it this.ClientTrySetActiveState(item, setIsActive: false); if (!publicState.IsActive) { return(true); } // still active! NotificationSystem.ClientShowNotification( NotificationCannotRefillWhileOn_Title, NotificationCannotRefillWhileOn_Message, color: NotificationColor.Bad, icon: this.Icon); return(false); }
public override bool CanRemoveItem(CanRemoveItemContext context) { if (!(context.Item.ProtoItem is IProtoItemEquipmentImplant protoItemEquipment)) { // impossible - how did it end up here? return(true); } var itemEquipmentType = protoItemEquipment.EquipmentType; if (itemEquipmentType == EquipmentType.Implant) { // implant item if (context.ByCharacter == null || CreativeModeSystem.SharedIsInCreativeMode(context.ByCharacter)) { // Allowed to add/remove implant item by the game only (via medical station). // But allow to characters in the creative mode to do this directly. return(true); } if (IsClient) { NotificationSystem.ClientShowNotification( NotificationUseStationToRemoveImplant_Title, NotificationUseStationToRemoveImplant_Message, NotificationColor.Bad, protoItemEquipment.Icon); } return(false); } // can remove anything return(true); }
private void ClientApplyWorldSizeSliceExpansion() { var location = this.settingsViewModel.ViewModelSelectWorldSizeSliceExpansionLocation; var insertedArea = new BoundsUshort(new Vector2Ushort(location.OffsetX, location.OffsetY), new Vector2Ushort(location.SizeX, location.SizeY)); if (insertedArea.Size == Vector2Ushort.Zero) { NotificationSystem.ClientShowNotification("Please enter the size of expansion", color: NotificationColor.Bad); return; } if (insertedArea.Size.X % ScriptingConstants.WorldChunkSize != 0 || insertedArea.Size.Y % ScriptingConstants.WorldChunkSize != 0) { NotificationSystem.ClientShowNotification( $"Please ensure that the size of expansion can be divided on the world chunk size ({ScriptingConstants.WorldChunkSize}) without the remainder", color: NotificationColor.Bad); return; } this.CallServer(_ => _.ServerRemote_ApplyWorldSizeSliceExpansion(insertedArea)); }
public static void ClientTryReloadOrSwitchAmmoType( bool isSwitchAmmoType, bool sendToServer = true, bool?showNotificationIfNoAmmo = null) { var character = Api.Client.Characters.CurrentPlayerCharacter; var currentWeaponState = PlayerCharacter.GetPrivateState(character).WeaponState; var itemWeapon = currentWeaponState.ItemWeapon; if (itemWeapon is null) { // no active weapon to reload return; } var protoWeapon = (IProtoItemWeapon)itemWeapon.ProtoItem; if (protoWeapon.AmmoCapacity == 0) { // the item is non-reloadable return; } var itemPrivateState = itemWeapon.GetPrivateState <WeaponPrivateState>(); var ammoCountNeed = isSwitchAmmoType ? protoWeapon.AmmoCapacity : (ushort)Math.Max(0, protoWeapon.AmmoCapacity - itemPrivateState.AmmoCount); if (ammoCountNeed == 0) { Logger.Info("No need to reload the weapon " + itemWeapon, character); return; } var compatibleAmmoGroups = SharedGetCompatibleAmmoGroups(character, protoWeapon); if (compatibleAmmoGroups.Count == 0 && !isSwitchAmmoType) { if (showNotificationIfNoAmmo.HasValue && showNotificationIfNoAmmo.Value || currentWeaponState.SharedGetInputIsFiring()) { protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Empty, character, volume: SoundConstants.VolumeWeapon); NotificationSystem.ClientShowNotification( NotificationNoAmmo_Title, NotificationNoAmmo_Message, NotificationColor.Bad, protoWeapon.Icon, playSound: false); } if (currentWeaponState.SharedGetInputIsFiring()) { // stop firing the weapon currentWeaponState.ProtoWeapon.ClientItemUseFinish(itemWeapon); } return; } IProtoItemAmmo selectedProtoItemAmmo = null; var currentReloadingState = currentWeaponState.WeaponReloadingState; if (currentReloadingState is null) { // don't have reloading state - find ammo item matching current weapon ammo type var currentProtoItemAmmo = itemPrivateState.CurrentProtoItemAmmo; if (currentProtoItemAmmo is null) { // no ammo selected in weapon selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo: null)?.Key; } else // if weapon already has ammo { if (isSwitchAmmoType) { selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo)?.Key; if (selectedProtoItemAmmo == currentProtoItemAmmo && itemPrivateState.AmmoCount >= protoWeapon.AmmoCapacity) { // this ammo type is already loaded and it's fully reloaded Logger.Info("No need to reload the weapon " + itemWeapon, character); return; } } else // simple reload requested { // try to find ammo of the same type as already loaded into the weapon var isFound = false; foreach (var ammoGroup in compatibleAmmoGroups) { if (ammoGroup.Key == currentProtoItemAmmo) { isFound = true; selectedProtoItemAmmo = currentProtoItemAmmo; break; } } if (!isFound) { // no group selected - select first isSwitchAmmoType = true; sendToServer = true; selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentProtoItemAmmo: null)?.Key; } } } } else { if (!isSwitchAmmoType) { // already reloading return; } // already reloading - try select another ammo type (alternate between them) var currentReloadingProtoItemAmmo = currentReloadingState.ProtoItemAmmo; selectedProtoItemAmmo = SharedFindNextAmmoGroup(protoWeapon.CompatibleAmmoProtos, compatibleAmmoGroups, currentReloadingProtoItemAmmo)?.Key; if (selectedProtoItemAmmo == currentReloadingProtoItemAmmo) { // already reloading this ammo type return; } } if (currentReloadingState != null && currentReloadingState.ProtoItemAmmo == selectedProtoItemAmmo) { // already reloading with these ammo items return; } if (currentReloadingState is null && selectedProtoItemAmmo is null && itemPrivateState.CurrentProtoItemAmmo is null) { // already unloaded return; } // create reloading state on the Client-side var weaponReloadingState = new WeaponReloadingState( character, itemWeapon, protoWeapon, selectedProtoItemAmmo); currentWeaponState.WeaponReloadingState = weaponReloadingState; protoWeapon.SoundPresetWeapon.PlaySound(WeaponSound.Reload, character, SoundConstants.VolumeWeapon); Logger.Info( $"Weapon reloading started for {itemWeapon} reload duration: {weaponReloadingState.SecondsToReloadRemains:F2}s", character); if (weaponReloadingState.SecondsToReloadRemains <= 0) { // instant-reload weapon - perform local reloading SharedProcessWeaponReload(character, currentWeaponState, out _); } if (sendToServer || isSwitchAmmoType) { // perform reload on server var arg = new ReloadWeaponRequest(itemWeapon, selectedProtoItemAmmo); Instance.CallServer(_ => _.ServerRemote_ReloadWeapon(arg)); } }
public static bool SharedCheckCanInteract( ICharacter character, IDynamicWorldObject vehicle, bool writeToLog) { if (vehicle is null || vehicle.IsDestroyed) { return(false); } // it's possible to repair any vehicle within a certain distance to the character var canInteract = character.Position.DistanceSquaredTo(vehicle.Position) <= MaxDistanceForRepairAction * MaxDistanceForRepairAction; if (!canInteract) { if (writeToLog) { Logger.Warning( $"Character cannot interact with {vehicle} for repair - too far", character); if (IsClient) { CannotInteractMessageDisplay.ClientOnCannotInteract(vehicle, CoreStrings.Notification_TooFar, isOutOfRange: true); } } return(false); } var physicsSpace = character.PhysicsBody.PhysicsSpace; var characterCenter = character.Position + character.PhysicsBody.CenterOffset; if (ObstacleTestHelper.SharedHasObstaclesInTheWay(characterCenter, physicsSpace, vehicle, sendDebugEvents: writeToLog)) { if (writeToLog) { Logger.Warning( $"Character cannot interact with {vehicle} for repair - obstacles in the way", character); if (IsClient) { CannotInteractMessageDisplay.ClientOnCannotInteract(vehicle, CoreStrings.Notification_ObstaclesOnTheWay, isOutOfRange: true); } } return(false); } using var tempCharactersNearby = Api.Shared.GetTempList <ICharacter>(); if (IsClient) { Client.Characters.GetKnownPlayerCharacters(tempCharactersNearby); } else { Server.World.GetScopedByPlayers(vehicle, tempCharactersNearby); } foreach (var otherPlayerCharacter in tempCharactersNearby.AsList()) { if (ReferenceEquals(character, otherPlayerCharacter)) { continue; } if (PlayerCharacter.GetPublicState(otherPlayerCharacter).CurrentPublicActionState is VehicleRepairActionState.PublicState repairActionState && ReferenceEquals(repairActionState.TargetWorldObject, vehicle)) { // already repairing by another player if (!writeToLog) { return(false); } Logger.Important($"Cannot start repairing {vehicle} - already repairing by another player", character); if (IsClient) { NotificationSystem.ClientShowNotification( CoreStrings.Notification_ErrorCannotInteract, CoreStrings.Notification_ErrorObjectUsedByAnotherPlayer, NotificationColor.Bad, icon: ((IProtoVehicle)vehicle.ProtoGameObject).Icon); } return(false); } } return(true); }
private static void InvitationsCollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e) { string inviterName; switch (e.Action) { case NotifyCollectionChangedAction.Add: { inviterName = (string)e.NewItems[0]; ShowNotification(inviterName); break; } case NotifyCollectionChangedAction.Remove: { inviterName = (string)e.OldItems[0]; if (NotificationsFromInviteeDictionary.TryGetValue(inviterName, out var weakReference) && weakReference.TryGetTarget(out var control)) { control.Hide(quick: true); } NotificationsFromInviteeDictionary.Remove(inviterName); break; } case NotifyCollectionChangedAction.Reset: { // hide all current notifications foreach (var notificationControl in NotificationsFromInviteeDictionary) { var weakReference = notificationControl.Value; if (weakReference.TryGetTarget(out var control)) { control.Hide(quick: true); } } NotificationsFromInviteeDictionary.Clear(); // display new notifications foreach (var name in PartySystem.ClientCurrentInvitationsFromCharacters) { ShowNotification(name); } break; } default: throw new ArgumentOutOfRangeException(); } void ShowNotification(string name) { if (ClientChatBlockList.IsBlocked(name)) { // don't display invitations from blocked players return; } var control = NotificationSystem.ClientShowNotification( title: PartyInvitationTitle, message: string.Format(InvitationMessageFormat, name), onClick: () => ShowInvitationDialog(name), autoHide: false, icon: IconPartyInvitation); control.CallbackOnRightClickHide = () => PartySystem.ClientInvitationDecline(name); NotificationsFromInviteeDictionary.Add( name, new WeakReference <HudNotificationControl>(control)); } }
public override bool CanAddItem(CanAddItemContext context) { if (context.Item.ProtoItem is not IProtoItemEquipment protoItemEquipment) { // not an equipment - cannot be placed here return(false); } if (!context.SlotId.HasValue) { // no specific slot provided - so answer "true" because any equipment item could be added here return(true); } var slotIdValue = (EquipmentType)context.SlotId.Value; var itemEquipmentType = protoItemEquipment.EquipmentType; if (!context.IsExploratoryCheck && (itemEquipmentType == EquipmentType.Armor || itemEquipmentType == EquipmentType.FullBody) && !StatusEffectPeredozinApplication.SharedCheckCanEquipArmor(context.Container.OwnerAsCharacter, clientShowNotification: true)) { // don't allow equipping armor while peredozin is applying return(false); } if (itemEquipmentType == EquipmentType.FullBody) { if (slotIdValue != EquipmentType.Armor) { // cannot place full body armor in another slots return(false); } if (context.IsExploratoryCheck) { // no more checks return(true); } // It's actual operation, perform more checks! // Need to verify that head is an empty slot // Please note: can equip full body when there is an armor without the head item - will swap items. var container = context.Container; if (IsSlotEmpty(container, EquipmentType.Head)) { // can place here return(true); } // head and legs are not empty if (IsClient) { NotificationSystem.ClientShowNotification( NotificationCannotEquip, NotificationRemoveOtherEquipment, NotificationColor.Bad, protoItemEquipment.Icon); } return(false); } var isValidSlot = protoItemEquipment.CompatibleContainerSlotsIds.Contains((byte)slotIdValue); if (!isValidSlot) { // the selected slot doesn't match the equipment return(false); } if (itemEquipmentType == EquipmentType.Implant) { // implant item if (context.ByCharacter is null || CreativeModeSystem.SharedIsInCreativeMode(context.ByCharacter)) { // Allowed to add/remove implant item by the game only (via medical station). // But allow to characters in the creative mode to do this directly. return(true); } if (IsClient && !context.IsExploratoryCheck && protoItemEquipment is not ItemImplantBroken) { NotificationSystem.ClientShowNotification( NotificationUseStationToInstallImplant_Title, NotificationUseStationToInstallImplant_Message, NotificationColor.Bad, protoItemEquipment.Icon); } return(false); } if (!context.IsExploratoryCheck && itemEquipmentType == EquipmentType.Head) { // Regular equipment - can equip head ONLY if there is no full body armor. // Please note: can equip regular armor (without the helmet) even if there is full body armor - will swap items. if (IsHasFullBodyArmor(context.Container)) { // has full body armor - cannot equip this item if (IsClient) { NotificationSystem.ClientShowNotification( NotificationCannotEquip, NotificationRemoveFullBody, NotificationColor.Bad, protoItemEquipment.Icon); } return(false); } } return(true); }
private static void SharedStartAction(ICharacter character, IWorldObject worldObject) { if (worldObject == null) { return; } if (!SharedCheckCanInteract(character, worldObject, writeToLog: true)) { return; } if (!(worldObject.ProtoGameObject is IProtoObjectStructure)) { throw new Exception("Not a structure: " + worldObject); } var characterPrivateState = PlayerCharacter.GetPrivateState(character); var characterPublicState = PlayerCharacter.GetPublicState(character); if (characterPrivateState.CurrentActionState is DeconstructionActionState actionState && actionState.WorldObject == worldObject) { // already deconstructing return; } var selectedHotbarItem = characterPublicState.SelectedHotbarItem; if (!(selectedHotbarItem?.ProtoGameObject is IProtoItemToolCrowbar)) { selectedHotbarItem = null; if (!(worldObject.ProtoWorldObject is ProtoObjectConstructionSite)) { // no crowbar tool is selected, only construction sites can be deconstructed without the crowbar return; } } actionState = new DeconstructionActionState(character, (IStaticWorldObject)worldObject, selectedHotbarItem); if (!actionState.CheckIsAllowed()) { // not allowed to deconstruct Logger.Warning( $"Deconstruction is not allowed: {worldObject} by {character}", character); if (Api.IsClient) { NotificationSystem.ClientShowNotification( NotificationNotLandOwner_Title, NotificationNotLandOwner_Message, NotificationColor.Bad, selectedHotbarItem?.ProtoItem.Icon); } return; } if (!actionState.CheckIsNeeded()) { // action is not needed Logger.Important($"Deconstruction is not required: {worldObject} by {character}", character); return; } characterPrivateState.SetCurrentActionState(actionState); Logger.Important($"Deconstruction started: {worldObject} by {character}", character); if (IsClient) { // TODO: display crowbar started animation? Send animation to other players? Instance.CallServer(_ => _.ServerRemote_StartAction(worldObject)); } }
private void ClientRemote_UnstuckAlreadyQueued() { NotificationSystem.ClientShowNotification( NotificationAlreadyRequestedUnstuck_Title, NotificationAlreadyRequestedUnstuck_Message); }
public static void ClientPaste(Vector2Ushort tilePosition) { if (lastBufferEntry is null) { return; } var bufferEntry = lastBufferEntry.Value; if (ClientEditorAreaSelectorHelper.Instance is not null) { NotificationSystem.ClientShowNotification( title: null, message: "You're already in object placement mode." + ObjectPlacementGuide, color: NotificationColor.Neutral); return; } NotificationSystem.ClientShowNotification( title: null, message: $"{bufferEntry.SpawnList.Count} objects ready for paste!" + ObjectPlacementGuide, color: NotificationColor.Good); var originalTileStartX = bufferEntry.TilePositions.Min(t => t.X); var originalTileStartY = bufferEntry.TilePositions.Min(t => t.Y); var originalTileEndX = bufferEntry.TilePositions.Max(t => t.X); var originalTileEndY = bufferEntry.TilePositions.Max(t => t.Y); var originalSize = new Vector2Ushort((ushort)(originalTileEndX - originalTileStartX + 1), (ushort)(originalTileEndY - originalTileStartY + 1)); // ReSharper disable once ObjectCreationAsStatement new ClientEditorAreaSelectorHelper(tilePosition, originalSize, selectedCallback: PlaceSelectedCallback); void PlaceSelectedCallback(Vector2Ushort selectedTilePosition) { var originalStartX = bufferEntry.SpawnList.Min(t => t.TilePosition.X); var originalStartY = bufferEntry.SpawnList.Min(t => t.TilePosition.Y); var offset = selectedTilePosition.ToVector2Int() - (originalStartX, originalStartY); var storageEntry = new ActionStorageEntry( spawnBatches: bufferEntry.SpawnList .Select(t => new SpawnObjectRequest( t.Prototype, t.TilePosition + offset)) .Batch(20000) .Select(b => b.ToList()) .ToList()); EditorClientActionsHistorySystem.DoAction( "Paste objects", onDo: async() => { var spawnBatches = storageEntry.SpawnBatches; for (var index = 0; index < spawnBatches.Count; index++) { var batch = spawnBatches[index]; var spawnedObjectsIds = await WorldObjectsEditingSystem.Instance.CallServer( _ => _.ServerRemote_SpawnObjects(batch)); // record spawned objects IDs so they could be removed on undo var undoSpawnBatch = storageEntry.UndoSpawnBatches[index]; undoSpawnBatch.Clear(); undoSpawnBatch.AddRange(spawnedObjectsIds); } NotificationSystem.ClientShowNotification( title: null, message: spawnBatches.Sum(r => r.Count) + " object(s) pasted!", color: NotificationColor.Good); }, onUndo: () => { var countRemoved = 0; foreach (var batch in storageEntry.UndoSpawnBatches) { countRemoved += batch.Count; WorldObjectsEditingSystem.Instance.CallServer( _ => _.ServerRemote_DeleteObjects(batch)); // the delete batch is no longer valid batch.Clear(); } NotificationSystem.ClientShowNotification( title: null, message: countRemoved + " objects(s) removed.", color: NotificationColor.Neutral); }, canGroupWithPreviousAction: false); } }
public static bool SharedCheckCanInteract( ICharacter character, IWorldObject worldObject, bool writeToLog) { if (worldObject is null || worldObject.IsDestroyed) { return(false); } var staticWorldObject = (IStaticWorldObject)worldObject; if (!staticWorldObject.ProtoStaticWorldObject.SharedCanInteract(character, worldObject, writeToLog)) { return(false); } using var tempCharactersNearby = Api.Shared.GetTempList <ICharacter>(); if (IsClient) { Client.Characters.GetKnownPlayerCharacters(tempCharactersNearby); } else { Server.World.GetScopedByPlayers(staticWorldObject, tempCharactersNearby); } foreach (var otherPlayerCharacter in tempCharactersNearby.AsList()) { if (ReferenceEquals(character, otherPlayerCharacter)) { continue; } if (PlayerCharacter.GetPublicState(otherPlayerCharacter).CurrentPublicActionState is HackingActionState.PublicState hackingState && ReferenceEquals(hackingState.TargetWorldObject, staticWorldObject)) { // already hacking by another player if (!writeToLog) { return(false); } Logger.Important($"Cannot start hacking {staticWorldObject} - already hacking by another player", character); if (IsClient) { NotificationSystem.ClientShowNotification( CoreStrings.Notification_ErrorCannotInteract, CoreStrings.Notification_ErrorObjectUsedByAnotherPlayer, NotificationColor.Bad, icon: staticWorldObject.ProtoStaticWorldObject.Icon); } return(false); } } return(true); }
public static void ClientActivateShield(ILogicObject areasGroup) { var status = SharedGetShieldPublicStatus(areasGroup); if (status != ShieldProtectionStatus.Inactive) { Logger.Warning("The shield is already active or activating"); return; } if (LandClaimSystem.SharedIsAreasGroupUnderRaid(areasGroup)) { NotificationSystem.ClientShowNotification(CoreStrings.ShieldProtection_CannotActivateDuringRaidBlock, color: NotificationColor.Bad); return; } SharedGetShieldProtectionMaxStatsForBase( areasGroup, out var maxShieldDuration, out var electricityCapacity); var privateState = LandClaimAreasGroup.GetPrivateState(areasGroup); var electricityAmount = privateState.ShieldProtectionCurrentChargeElectricity; var durationEstimation = maxShieldDuration * electricityAmount / electricityCapacity; var message = string.Format( CoreStrings.ShieldProtection_ActivationConfirmation_ProtectionDuration_Format, ClientTimeFormatHelper.FormatTimeDuration( durationEstimation, appendSeconds: false)); var cooldownRemains = SharedCalculateCooldownRemains(areasGroup); message += "[br][br]"; if (cooldownRemains <= 0) { message += string.Format(CoreStrings.ShieldProtection_ActivationConfirmation_DelayDuration_Format, ClientTimeFormatHelper.FormatTimeDuration( SharedActivationDuration, appendSeconds: true)); } else { message += string.Format( CoreStrings.ShieldProtection_ActivationConfirmation_DelayDurationWithCooldown_Format, ClientTimeFormatHelper.FormatTimeDuration( SharedActivationDuration + cooldownRemains, appendSeconds: true)); } message += "[br][br]"; message += string.Format(CoreStrings.ShieldProtection_DeactivationNotes_Format, ClientTimeFormatHelper.FormatTimeDuration( SharedCooldownDuration, appendSeconds: false)); message += "[br][br]"; message += CoreStrings.ShieldProtection_Description_9; DialogWindow.ShowDialog( CoreStrings.ShieldProtection_Dialog_ConfirmActivation, message, okText: CoreStrings.ShieldProtection_Button_ActivateShield, okAction: () => Instance.CallServer(_ => _.ServerRemote_ActivateShield(areasGroup)), cancelAction: () => { }, focusOnCancelButton: true); }
private void ClientRemote_UnstuckImpossible() { NotificationSystem.ClientShowNotification( NotificationUnstuckImpossible_Title, color: NotificationColor.Bad); }
public ItemFuelRefillRequest ClientTryCreateRequest(ICharacter character, IItem item) { if (!(item?.ProtoGameObject is IProtoItemWithFuel protoItemFuelRefillable)) { // no item selected return(null); } if (protoItemFuelRefillable.ItemFuelConfig.FuelProtoItemsList.Count == 0) { if (IsServer) { Logger.Warning("Cannot refill - not refillable: " + item, character); } else // if (IsClient) { NotificationSystem.ClientShowNotification( NotificationNotRefillable, color: NotificationColor.Bad, icon: protoItemFuelRefillable.Icon); } return(null); } if (!SharedIsRefuelNeeded(item)) { if (IsClient) { NotificationSystem.ClientShowNotification( NotificationNoNeedToRefill, color: NotificationColor.Neutral, icon: protoItemFuelRefillable.Icon); } return(null); } if (!protoItemFuelRefillable.ClientCanStartRefill(item)) { return(null); } var fuelItemsToConsume = ClientSelectItemsToConsume(protoItemFuelRefillable, amountNeeded: SharedGetFuelAmountNeeded(item), MaxItemsToConsumePerRefill); if (fuelItemsToConsume.Count > 0) { return(new ItemFuelRefillRequest(character, item, fuelItemsToConsume)); } NotificationSystem.ClientShowNotification( NotificationNeedFuel_Title, message: string.Format(NotificationNeedFuel_MessageFormat, protoItemFuelRefillable.ItemFuelConfig.FuelProtoItemsList.Select(i => i.Name) .GetJoinedString(", ")), color: NotificationColor.Bad, icon: protoItemFuelRefillable.Icon); return(null); }
private static void SharedStartAction(ICharacter character, IWorldObject worldObject) { if (!(worldObject?.ProtoGameObject is IProtoObjectStructure)) { return; } var characterPrivateState = PlayerCharacter.GetPrivateState(character); var characterPublicState = PlayerCharacter.GetPublicState(character); if (characterPrivateState.CurrentActionState is ConstructionActionState actionState && actionState.WorldObject == worldObject) { // already building/repairing specified object return; } var selectedHotbarItem = characterPublicState.SelectedHotbarItem; if (!(selectedHotbarItem?.ProtoGameObject is IProtoItemToolToolbox)) { // no tool is selected return; } actionState = new ConstructionActionState(character, (IStaticWorldObject)worldObject, selectedHotbarItem); if (!actionState.CheckIsNeeded()) { // action is not needed Logger.Info($"Building/repairing is not required: {worldObject} by {character}", character); return; } if (!SharedCheckCanInteract(character, worldObject, writeToLog: true)) { return; } if (!actionState.Config.IsAllowed) { // not allowed to build/repair // this code should never execute for a valid client Logger.Warning( $"Building/repairing is not allowed by object build/repair config: {worldObject} by {character}", character); if (Api.IsClient) { NotificationSystem.ClientShowNotification( // ReSharper disable once CanExtractXamlLocalizableStringCSharp "Cannot construct", // ReSharper disable once CanExtractXamlLocalizableStringCSharp "Not constructable.", NotificationColor.Bad, selectedHotbarItem.ProtoItem.Icon); } return; } if (!actionState.ValidateRequiredItemsAvailable()) { // action is not possible // logging is not required here - it's done automatically by the validation method return; } characterPrivateState.SetCurrentActionState(actionState); Logger.Info($"Building/repairing started: {worldObject} by {character}", character); if (IsClient) { // TODO: we need animation for building/repairing Instance.CallServer(_ => _.ServerRemote_StartAction(worldObject)); } }
private static void SharedStartAction(ICharacter character, IWorldObject worldObject) { if (worldObject is null) { return; } if (!SharedCheckCanInteract(character, worldObject, writeToLog: true)) { return; } if (worldObject.ProtoGameObject is not IProtoObjectStructure protoObjectStructure) { throw new Exception("Not a structure: " + worldObject); } if (!SharedIsDeconstructable(protoObjectStructure)) { throw new Exception("Not deconstructable: " + worldObject); } var characterPrivateState = PlayerCharacter.GetPrivateState(character); var characterPublicState = PlayerCharacter.GetPublicState(character); if (characterPrivateState.CurrentActionState is DeconstructionActionState actionState && actionState.WorldObject == worldObject) { // already deconstructing return; } var selectedHotbarItem = characterPublicState.SelectedItem; if (selectedHotbarItem?.ProtoGameObject is not IProtoItemToolCrowbar) { selectedHotbarItem = null; if (worldObject.ProtoWorldObject is not ProtoObjectConstructionSite) { // no crowbar tool is selected, only construction sites // can be deconstructed without the crowbar return; } } actionState = new DeconstructionActionState(character, (IStaticWorldObject)worldObject, selectedHotbarItem); if (!actionState.CheckIsAllowed(out var hasNoFactionPermission)) { // not allowed to deconstruct Logger.Warning( $"Deconstruction is not allowed: {worldObject} by {character}", character); if (Api.IsClient) { if (hasNoFactionPermission) { NotificationSystem.ClientShowNotification( title: CoreStrings.Notification_ActionForbidden, string.Format(CoreStrings.Faction_Permission_Required_Format, CoreStrings.Faction_Permission_LandClaimManagement_Title), NotificationColor.Bad, selectedHotbarItem?.ProtoItem.Icon); } else { NotificationSystem.ClientShowNotification( NotificationCannotDeconstruct_Title, LandClaimSystem.ErrorNotLandOwner_Message, NotificationColor.Bad, selectedHotbarItem?.ProtoItem.Icon); } } return; } if (!actionState.CheckIsNeeded()) { // action is not needed Logger.Important($"Deconstruction is not required: {worldObject} by {character}", character); return; } characterPrivateState.SetCurrentActionState(actionState); Logger.Important($"Deconstruction started: {worldObject} by {character}", character); if (IsClient) { // TODO: display crowbar started animation? Send animation to other players? Instance.CallServer(_ => _.ServerRemote_StartAction(worldObject)); } }
private void ClientRemote_UnstuckSuccessful() { NotificationSystem.ClientShowNotification( NotificationUnstuckSuccessful_Title, NotificationUnstuckSuccessful_Message); }
public static async void ClientTryDropItemOnGround( IItem itemToDrop, ushort countToDrop, Vector2Ushort?dropTilePosition = null) { countToDrop = Math.Min(countToDrop, itemToDrop.Count); var character = Client.Characters.CurrentPlayerCharacter; if (!dropTilePosition.HasValue) { if (ClientTryDropItemToGroundContainerNearby( character.Tile, itemToDrop, countToDrop, out dropTilePosition, out var resultItemsContainer)) { OnSuccess(resultItemsContainer); return; } countToDrop = Math.Min(countToDrop, itemToDrop.Count); var obstaclesOnTheWay = false; if (!dropTilePosition.HasValue || !SharedIsWithinInteractionDistance( character, dropTilePosition.Value, out obstaclesOnTheWay)) { NotificationSystem.ClientShowNotification( obstaclesOnTheWay ? CoreStrings.Notification_ObstaclesOnTheWay : NotificationNoFreeSpaceToDrop, color: NotificationColor.Bad, icon: TextureResourceSack); return; } } var tilePosition = dropTilePosition.Value; if (!SharedIsWithinInteractionDistance( character, tilePosition, out var obstaclesOnTheWay2)) { NotificationSystem.ClientShowNotification( obstaclesOnTheWay2 ? CoreStrings.Notification_ObstaclesOnTheWay : CoreStrings.Notification_TooFar, NotificationCannotDropItemThere, NotificationColor.Bad, TextureResourceSack); return; } var tile = Client.World.GetTile(tilePosition); var objectGroundContainer = tile.StaticObjects.FirstOrDefault(_ => _.ProtoGameObject == instance); if (objectGroundContainer is null) { if (!instance.CheckTileRequirements(tilePosition, character, logErrors: false)) { // cannot drop item here NotificationSystem.ClientShowNotification( CoreStrings.Notification_ObstaclesOnTheWay, NotificationCannotDropItemThere, NotificationColor.Bad, TextureResourceSack); return; } Logger.Info( $"Requested placing item on the ground (new ground container needed): {itemToDrop}. Count={countToDrop}."); objectGroundContainer = await instance.CallServer( _ => _.ServerRemote_DropItemOnGround( itemToDrop, countToDrop, tilePosition)); if (objectGroundContainer != null) { // successfully placed on ground OnSuccess(GetPublicState(objectGroundContainer).ItemsContainer); return; } // we're continue the async call - the context might have been changed if (itemToDrop.IsDestroyed) { return; } // was unable to place the item on the ground - maybe it was already placed with an earlier call if (itemToDrop.Container?.OwnerAsStaticObject?.ProtoStaticWorldObject is ObjectGroundItemsContainer) { // it seems to be on the ground now return; } // the action is definitely failed instance.SoundPresetObject.PlaySound(ObjectSound.InteractFail); return; } if (!instance.SharedCanInteract(character, objectGroundContainer, writeToLog: true)) { return; } // get items container instance var groundItemsContainer = GetPublicState(objectGroundContainer).ItemsContainer; // try move item to the ground items container if (!Client.Items.MoveOrSwapItem( itemToDrop, groundItemsContainer, countToMove: countToDrop, isLogErrors: false)) { // cannot move - open container UI ClientOpenContainerExchangeUI(objectGroundContainer); return; } // item moved successfully OnSuccess(groundItemsContainer); void OnSuccess(IItemsContainer resultGroundItemsContainer) { itemToDrop.ProtoItem.ClientOnItemDrop(itemToDrop, resultGroundItemsContainer); NotificationSystem.ClientShowItemsNotification( itemsChangedCount: new Dictionary <IProtoItem, int>() { { itemToDrop.ProtoItem, -countToDrop } }); if (Api.Client.Input.IsKeyHeld(InputKey.Shift, evenIfHandled: true)) { // open container UI to allow faster items exchange with it ClientOpenContainerExchangeUI(resultGroundItemsContainer.OwnerAsStaticObject); } } }
private void ClientShowProtectedBagNotification() { NotificationSystem.ClientShowNotification( title: NewbieProtectionSystem.Notification_LootBagUnderProtection, icon: this.Icon); }