public static void ServerReplaceConstructionSiteWithStructure( IStaticWorldObject worldObject, IProtoObjectStructure protoStructure, ICharacter byCharacter) { if (worldObject?.IsDestroyed ?? true) { throw new Exception("Construction site doesn't exist or already destroyed: " + worldObject); } var tilePosition = worldObject.TilePosition; // destroy construction site Server.World.DestroyObject(worldObject); // create structure var structure = ConstructionSystem.ServerCreateStructure( protoStructure, tilePosition, byCharacter: byCharacter); if (byCharacter == null) { return; } Instance.ServerNotifyOnStructurePlacedOrRelocated(structure, byCharacter); Api.SafeInvoke(() => ServerStructureBuilt?.Invoke(byCharacter, structure)); }
public override void SharedUpdate(double deltaTime) { if (this.CharacterPublicState.SelectedItem != this.ItemConstructionTool) { this.AbortAction(); return; } if (!ConstructionSystem.SharedCheckCanInteract( this.Character, this.WorldObject, writeToLog: true)) { this.AbortAction(); return; } this.currentStageTimeRemainsSeconds -= deltaTime; if (this.currentStageTimeRemainsSeconds <= 0) { this.SharedOnStageCompleted(); } else if (Api.IsClient) { if (this.ObjectPublicState.StructurePointsCurrent >= this.structurePointsMax) { // apparently the building finished construction/repair before the client simulation was complete this.AbortAction(); } } this.UpdateProgress(); }
public override void SharedUpdate(double deltaTime) { if (this.CharacterPublicState.SelectedHotbarItem != this.ItemConstructionTool) { this.AbortAction(); return; } if (!ConstructionSystem.SharedCheckCanInteract( this.Character, this.WorldObject, writeToLog: true)) { this.AbortAction(); return; } this.currentStageTimeRemainsSeconds -= deltaTime; if (this.currentStageTimeRemainsSeconds <= 0) { this.SharedOnStageCompleted(); } this.UpdateProgress(); }
private static void ClientValidateCanBuild( Vector2Ushort tilePosition, bool logErrors, out string errorMessage, out bool canPlace, out bool isTooFar) { var protoStructure = currentSelectedProtoConstruction; if (Client.World.GetTile(tilePosition) .StaticObjects .Any(so => so.ProtoStaticWorldObject == protoStructure || ProtoObjectConstructionSite.SharedIsConstructionOf(so, protoStructure))) { // this building is already built here canPlace = false; isTooFar = false; errorMessage = null; return; } var character = Client.Characters.CurrentPlayerCharacter; if (!protoStructure.CheckTileRequirements( tilePosition, character, errorCodeOrMessage: out var errorCodeOrMessage, logErrors: logErrors)) { // time requirements are not valid canPlace = false; isTooFar = false; errorMessage = ConstructionSystem.SharedConvertCodeOrErrorMessageToString(errorCodeOrMessage); return; } errorMessage = null; var configBuild = protoStructure.ConfigBuild; if (configBuild.CheckStageCanBeBuilt(character)) { canPlace = true; isTooFar = SharedIsTooFarToPlace(protoStructure, tilePosition, character, logErrors); return; } // not enough items to build the stage // close construction menu ClientCloseConstructionMenu(); canPlace = false; isTooFar = false; }
/// <summary> /// Verifies that the character has all the required items to use this action. /// </summary> public bool ValidateRequiredItemsAvailable() { if (this.Config.CheckStageCanBeBuilt(this.Character)) { return(true); } Api.Logger.Warning( $"Building/repairing is not possible - not all required items are available: {this.WorldObject} by {this.Character}", this.Character); ConstructionSystem.SharedOnNotEnoughItemsAvailable(this); return(false); }
private void ServerRemote_PlaceStructure( IProtoObjectStructure protoStructure, Vector2Ushort tilePosition) { var character = ServerRemoteContext.Character; if (!protoStructure.SharedIsTechUnlocked(character)) { Logger.Error( $"Cannot build {protoStructure} at {tilePosition}: player character doesn't have unlocked tech node for this structure.", character); return; } if (SharedIsTooFarToPlace(protoStructure, tilePosition, character, logErrors: true)) { return; } // validate if the structure can be placed there if (!protoStructure.CheckTileRequirements(tilePosition, character, logErrors: true)) { return; } // validate if there are enough required items/resources to build the structure var configBuild = protoStructure.ConfigBuild; if (!configBuild.CheckStageCanBeBuilt(character)) { Logger.Error( $"Cannot build {protoStructure} at {tilePosition}: player character doesn't have enough resources (or not allowed).", character); return; } var selectedHotbarItem = PlayerCharacter.GetPublicState(character).SelectedItem; if (!(selectedHotbarItem?.ProtoItem is IProtoItemToolToolbox)) { Logger.Error( $"Cannot build {protoStructure} at {tilePosition}: player character doesn't have selected construction tool.", character); return; } // consume required items/resources (for 1 stage) configBuild.ServerDestroyRequiredItems(character); if (configBuild.StagesCount > 1) { if (AllowInstantPlacementInCreativeMode && CreativeModeSystem.SharedIsInCreativeMode(character)) { // instant placement allowed } else { // there are multiple construction stages - spawn and setup a construction site var constructionSite = this.ServerCreateConstructionSite(tilePosition, protoStructure, character); this.ServerNotifyOnStructurePlacedOrRelocated(constructionSite, character); return; } } ServerDecalsDestroyHelper.DestroyAllDecals(tilePosition, protoStructure.Layout); // there is only one construction stage - simply spawn the structure var structure = ConstructionSystem.ServerCreateStructure( protoStructure, tilePosition, character); this.ServerNotifyOnStructurePlacedOrRelocated(structure, character); Api.SafeInvoke(() => ServerStructureBuilt?.Invoke(character, structure)); }
public static void ClientStartRelocation(IStaticWorldObject objectStructure) { var protoStructure = objectStructure.ProtoStaticWorldObject; var character = Client.Characters.CurrentPlayerCharacter; if (IsInObjectPlacementMode || ConstructionPlacementSystem.IsInObjectPlacementMode) { // already relocating/placing something return; } if (!SharedIsRelocatable(objectStructure)) { return; } if (!CreativeModeSystem.SharedIsInCreativeMode(character) && !LandClaimSystem.SharedIsOwnedLand(objectStructure.TilePosition, character, requireFactionPermission: true, out var hasNoFactionPermission, out _)) { // the building location or destination is in an area that is not owned by the player SharedShowCannotRelocateNotification( character, protoStructure, hasNoFactionPermission); return; } var isPvEorWithinInteractionArea = PveSystem.SharedIsPve(false) || protoStructure.SharedIsInsideCharacterInteractionArea( Api.Client.Characters.CurrentPlayerCharacter, objectStructure, writeToLog: false); if (!isPvEorWithinInteractionArea) { CannotInteractMessageDisplay.ClientOnCannotInteract( character, CoreStrings.Notification_TooFar, isOutOfRange: true); return; } if (LandClaimSystem.SharedIsUnderRaidBlock(character, objectStructure)) { // the building is in an area under the raid ConstructionSystem.SharedShowCannotBuildNotification( character, LandClaimSystem.ErrorRaidBlockActionRestricted_Message, protoStructure); return; } if (LandClaimShieldProtectionSystem.SharedIsUnderShieldProtection(objectStructure)) { // the building is in an area under shield protection LandClaimShieldProtectionSystem.SharedSendNotificationActionForbiddenUnderShieldProtection( character); return; } ClientDisableConstructionRelocation(); var sceneObject = Client.Scene.CreateSceneObject("StructureRelocationHelper"); componentObjectPlacementHelper = sceneObject.AddComponent <ClientComponentObjectPlacementHelper>(); componentRelocationHelper = sceneObject.AddComponent <ClientComponentObjectRelocationHelper>(); componentObjectPlacementHelper .Setup(protoStructure, isCancelable: true, isRepeatCallbackIfHeld: false, isDrawConstructionGrid: true, isBlockingInput: true, validateCanPlaceCallback: ClientValidateCanRelocate, placeSelectedCallback: ClientConstructionPlaceSelectedCallback, delayRemainsSeconds: 0.1); componentObjectPlacementHelper.HideBlueprintOnOverlapWithTheSameObject = false; componentRelocationHelper.Setup(objectStructure); void ClientValidateCanRelocate( Vector2Ushort tilePosition, bool logErrors, out string errorMessage, out bool canPlace, out bool isTooFar) { if (tilePosition == objectStructure.TilePosition) { canPlace = true; isTooFar = false; errorMessage = null; return; } if (!SharedCheckTileRequirementsForRelocation(character, objectStructure, tilePosition, out errorMessage, logErrors: logErrors)) { // time requirements are not valid canPlace = false; isTooFar = false; return; } if (!SharedValidateCanCharacterRelocateStructure(character, objectStructure, tilePosition, out errorMessage, logErrors: logErrors)) { canPlace = true; isTooFar = true; return; } if (SharedHasObstacle( character, objectStructure, tilePosition.ToVector2D() + protoStructure.Layout.Center)) { if (logErrors) { CannotInteractMessageDisplay.ClientOnCannotInteract( character, CoreStrings.Notification_ObstaclesOnTheWay, isOutOfRange: true); } errorMessage = CoreStrings.Notification_ObstaclesOnTheWay; canPlace = true; isTooFar = true; return; } canPlace = true; isTooFar = false; } void ClientConstructionPlaceSelectedCallback(Vector2Ushort tilePosition) { if (SharedHasObstacle( character, objectStructure, tilePosition.ToVector2D() + protoStructure.Layout.Center)) { CannotInteractMessageDisplay.ClientOnCannotInteract( character, CoreStrings.Notification_ObstaclesOnTheWay, isOutOfRange: true); return; } ClientTimersSystem.AddAction(0.1, ClientDisableConstructionRelocation); if (tilePosition != objectStructure.TilePosition) { Instance.CallServer(_ => _.ServerRemote_RelocateStructure(objectStructure, tilePosition)); } } }
private static bool SharedCheckTileRequirementsForRelocation( ICharacter character, IStaticWorldObject objectStructure, Vector2Ushort toPosition, out string errorMessage, bool logErrors) { if (!(objectStructure.ProtoGameObject is IProtoObjectStructure protoStructure)) { errorMessage = null; return(false); } var world = Api.IsServer ? (IWorldService)Server.World : (IWorldService)Client.World; var tileRequirements = protoStructure.TileRequirements; var occupiedTilePositions = new HashSet <Vector2Ushort>(objectStructure.OccupiedTilePositions); foreach (var tileOffset in protoStructure.Layout.TileOffsets) { var tilePosition = new Vector2Ushort((ushort)(toPosition.X + tileOffset.X), (ushort)(toPosition.Y + tileOffset.Y)); if (occupiedTilePositions.Contains(tilePosition)) { // no need to check that tile as the object player want to move is already there continue; } var tile = world.GetTile(tilePosition, logOutOfBounds: false); if (tile.IsOutOfBounds) { errorMessage = "Out of bounds"; } else { var context = new ConstructionTileRequirements.Context(tile, character, protoStructure, tileOffset, startTilePosition: toPosition, objectToRelocate: objectStructure); if (tileRequirements.Check(context, out errorMessage)) { // valid tile continue; } } // check failed if (!logErrors) { return(false); } if (Api.IsServer) { Api.Logger.Warning( $"Cannot move {protoStructure} at {toPosition} - check failed:" + Environment.NewLine + errorMessage); } ConstructionSystem.SharedShowCannotPlaceNotification( character, errorMessage, protoStructure); return(false); } errorMessage = null; return(true); }
public static bool SharedValidateCanCharacterRelocateStructure( ICharacter character, IStaticWorldObject objectStructure, Vector2Ushort toPosition, out string errorMessage, bool logErrors) { if (!SharedIsRelocatable(objectStructure)) { errorMessage = null; return(false); } if (!SharedCheckTileRequirementsForRelocation(character, objectStructure, toPosition, out errorMessage, logErrors)) { return(false); } if (!(objectStructure.ProtoGameObject is IProtoObjectStructure protoStructure)) { return(false); } var maxRelocationDistance = PveSystem.SharedIsPve(true) ? MaxRelocationDistancePvE : MaxRelocationDistancePvP; if (objectStructure.TilePosition.TileSqrDistanceTo(toPosition) > maxRelocationDistance * maxRelocationDistance) { if (logErrors) { ConstructionSystem.SharedShowCannotPlaceNotification( character, CoreStrings.Notification_TooFar, protoStructure); } errorMessage = CoreStrings.Notification_TooFar; return(false); } var itemInHands = character.SharedGetPlayerSelectedHotbarItem(); if (!(itemInHands.ProtoGameObject is IProtoItemToolToolbox)) { return(false); } if (CreativeModeSystem.SharedIsInCreativeMode(character)) { errorMessage = null; return(true); } if (!LandClaimSystem.SharedIsOwnedLand(objectStructure.TilePosition, character, requireFactionPermission: true, out var hasNoFactionPermission, ownedArea: out _) || !IsOwnedLand(toPosition, out hasNoFactionPermission)) { errorMessage = string.Format(CoreStrings.Faction_Permission_Required_Format, CoreStrings.Faction_Permission_LandClaimManagement_Title); // the building location or destination is in an area that is not owned by the player if (logErrors) { SharedShowCannotRelocateNotification( character, protoStructure, hasNoFactionPermission); } return(false); } if (LandClaimSystem.SharedIsUnderRaidBlock(character, objectStructure)) { // the building is in an area under the raid errorMessage = LandClaimSystem.ErrorRaidBlockActionRestricted_Message; if (logErrors) { ConstructionSystem.SharedShowCannotPlaceNotification( character, LandClaimSystem.ErrorRaidBlockActionRestricted_Message, protoStructure); } return(false); } if (LandClaimShieldProtectionSystem.SharedIsUnderShieldProtection(objectStructure)) { // the building is in an area under shield protection errorMessage = CoreStrings.ShieldProtection_ActionRestrictedBaseUnderShieldProtection; if (logErrors) { LandClaimShieldProtectionSystem.SharedSendNotificationActionForbiddenUnderShieldProtection( character); } return(false); } errorMessage = null; return(true); bool IsOwnedLand( Vector2Ushort startTilePosition, out bool hasNoFactionPermission) { var worldObjectLayoutTileOffsets = objectStructure.ProtoStaticWorldObject.Layout.TileOffsets; foreach (var tileOffset in worldObjectLayoutTileOffsets) { if (!LandClaimSystem.SharedIsOwnedLand(startTilePosition.AddAndClamp(tileOffset), character, requireFactionPermission: true, out hasNoFactionPermission, out _)) { return(false); } } hasNoFactionPermission = false; return(true); } }
private void SharedOnStageCompleted() { if (!this.ValidateRequiredItemsAvailable()) { // don't have required items - cannot do building/repairing action this.AbortAction(); return; } if (Api.IsServer) { // items are removing only on the Server-side this.Config.ServerDestroyRequiredItems(this.Character); // notify tool was used ServerItemUseObserver.NotifyItemUsed(this.Character, this.ItemConstructionTool); // reduce tool durability ItemDurabilitySystem.ServerModifyDurability(this.ItemConstructionTool, delta: -1); } this.currentStageDurationSeconds = this.CalculateStageDurationSeconds(this.Character, isFirstStage: false); this.currentStageTimeRemainsSeconds += this.currentStageDurationSeconds; var currentStructurePoints = this.ObjectPublicState.StructurePointsCurrent; var newStructurePoints = currentStructurePoints + this.stageStructureAddValue; // Please note: as we're using floating number (StructurePointsCurrent) to track the construction progress // it might cause some inaccuracy. In order to avoid this inaccuracy we're adding some tolerance. // The tolerance is also needed to handle the case when the blueprint was damaged only slightly. var completionTolerance = this.stageStructureAddValue / 2.0; // Please note: client will simply always construct until finished // Server will keep building stages until the completion tolerance reached. if ((Api.IsClient && currentStructurePoints < this.structurePointsMax) || (Api.IsServer && newStructurePoints + completionTolerance < this.structurePointsMax)) { // repairing/building is still possible - more stages are available if (Api.IsServer) { this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints; Api.Logger.Important( $"Building/repairing progressed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}"); if (this.IsRepair) { ((IProtoObjectStructure)this.WorldObject.ProtoStaticWorldObject) .ServerOnRepairStageFinished(this.WorldObject, this.Character); } } this.UpdateProgress(); if (!this.ValidateRequiredItemsAvailable()) { // don't have enough required items - cannot continue building/repairing action this.AbortAction(); return; } if (Api.IsServer && this.ItemConstructionTool.IsDestroyed) { // tool was destroyed (durability 0) this.AbortAction(); return; } return; } // repairing/building is completed if (Api.IsServer) { newStructurePoints = this.structurePointsMax; Api.Logger.Important( $"Building/repairing completed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}"); this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints; if (this.IsRepair) { ((IProtoObjectStructure)this.WorldObject.ProtoStaticWorldObject) .ServerOnRepairStageFinished(this.WorldObject, this.Character); } if (this.ObjectPublicState is ConstructionSitePublicState constructionSiteState) { constructionSiteState.LastBuildActionDoneByCharacter = this.Character; var constructionProto = ProtoObjectConstructionSite.SharedGetConstructionProto(this.WorldObject); // add skill experience for building this.CharacterPrivateState.Skills.ServerAddSkillExperience <SkillBuilding>( SkillBuilding.ExperienceAddWhenBuildingFinished * constructionProto.BuildingSkillExperienceMultiplier); } else { // add skill experience for repair this.CharacterPrivateState.Skills.ServerAddSkillExperience <SkillBuilding>( SkillBuilding.ExperienceAddWhenRepairFinished * ((IProtoObjectStructure)this.WorldObject.ProtoGameObject).BuildingSkillExperienceMultiplier); } } this.SetCompleted(isCancelled: false); ConstructionSystem.SharedActionCompleted(this.Character, this); }
protected override void AbortAction() { ConstructionSystem.SharedAbortAction( this.Character, this.WorldObject); }
public static bool SharedValidateCanCharacterRelocateStructure( ICharacter character, IStaticWorldObject objectStructure, Vector2Ushort toPosition, bool logErrors) { if (!SharedIsRelocatable(objectStructure)) { return(false); } if (!SharedCheckTileRequirementsForRelocation(character, objectStructure, toPosition, logErrors)) { return(false); } if (!(objectStructure.ProtoGameObject is IProtoObjectStructure protoStructure)) { return(false); } if (objectStructure.TilePosition.TileSqrDistanceTo(toPosition) > MaxRelocationDistance * MaxRelocationDistance) { if (logErrors) { ConstructionSystem.SharedShowCannotPlaceNotification( character, CoreStrings.Notification_TooFar, protoStructure); } return(false); } var itemInHands = character.SharedGetPlayerSelectedHotbarItem(); if (!(itemInHands.ProtoGameObject is IProtoItemToolToolbox)) { return(false); } if (CreativeModeSystem.SharedIsInCreativeMode(character)) { return(true); } if (!LandClaimSystem.SharedIsOwnedLand(objectStructure.TilePosition, character, out _) || !IsOwnedLand(toPosition)) { // the building location or destination is in an area that is not owned by the player if (logErrors) { ConstructionSystem.SharedShowCannotPlaceNotification( character, LandClaimSystem.ErrorNotLandOwner_Message, protoStructure); } return(false); } if (LandClaimSystem.SharedIsUnderRaidBlock(character, objectStructure)) { // the building is in an area under the raid if (logErrors) { ConstructionSystem.SharedShowCannotPlaceNotification( character, LandClaimSystem.ErrorRaidBlockActionRestricted_Message, protoStructure); } return(false); } if (LandClaimShieldProtectionSystem.SharedIsUnderShieldProtection(objectStructure)) { // the building is in an area under shield protection if (logErrors) { LandClaimShieldProtectionSystem.SharedSendNotificationActionForbiddenUnderShieldProtection( character); } return(false); } return(true); bool IsOwnedLand(Vector2Ushort startTilePosition) { var worldObjectLayoutTileOffsets = objectStructure.ProtoStaticWorldObject.Layout.TileOffsets; foreach (var tileOffset in worldObjectLayoutTileOffsets) { if (!LandClaimSystem.SharedIsOwnedLand(startTilePosition.AddAndClamp(tileOffset), character, out _)) { return(false); } } return(true); } }
private void SharedOnStageCompleted() { if (!this.ValidateRequiredItemsAvailable()) { // don't have required items - cannot do building/repairing action this.AbortAction(); return; } if (Api.IsServer) { // items are removing only on the Server-side this.Config.ServerDestroyRequiredItems(this.Character); // notify tool was used ServerItemUseObserver.NotifyItemUsed(this.Character, this.ItemConstructionTool); // reduce tool durability ItemDurabilitySystem.ServerModifyDurability(this.ItemConstructionTool, delta: -1); } this.currentStageDurationSeconds = this.CalculateStageDurationSeconds(this.Character, isFirstStage: false); this.currentStageTimeRemainsSeconds += this.currentStageDurationSeconds; var newStructurePoints = this.ObjectPublicState.StructurePointsCurrent + this.stageStructureAddValue; // Please note: as we're using floating number (StructurePointsCurrent) to track the construction progress // it might cause some inaccuracy. In order to avoid this inaccuracy we're adding some tolerance. // The tolerance is also needed to handle the case when the blueprint was damaged only slightly. var completionTolerance = this.stageStructureAddValue / 2.0; if (newStructurePoints + completionTolerance < this.structurePointsMax) { // repairing/building is still possible - more stages are available if (Api.IsServer) { this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints; Api.Logger.Important( $"Building/repairing progressed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}"); } this.UpdateProgress(); if (!this.ValidateRequiredItemsAvailable()) { // don't have enough required items - cannot continue building/repairing action this.AbortAction(); return; } if (Api.IsServer && this.ItemConstructionTool.IsDestroyed) { // tool was destroyed (durability 0) this.AbortAction(); return; } return; } // repairing/building is completed if (Api.IsServer) { // add building skill experience this.CharacterPrivateState.Skills.ServerAddSkillExperience <SkillBuilding>( SkillBuilding.ExperienceAddWhenBuildingFinished); newStructurePoints = this.structurePointsMax; Api.Logger.Important( $"Building/repairing completed: {this.WorldObject} structure points: {newStructurePoints}/{this.structurePointsMax}; by {this.Character}"); this.ObjectPublicState.StructurePointsCurrent = (float)newStructurePoints; if (this.ObjectPublicState is ConstructionSitePublicState constructionSiteState) { constructionSiteState.LastBuildActionDoneByCharacter = this.Character; } } else { // play success sound /*this.TargetWorldObject.ProtoWorldObject.*/ Api.GetProtoEntity <ObjectConstructionSite>().SharedGetObjectSoundPreset() .PlaySound(ObjectSound.InteractSuccess); } this.SetCompleted(isCancelled: false); ConstructionSystem.SharedActionCompleted(this.Character, this); }