private IStaticWorldObject ServerCreateConstructionSite( Vector2Ushort tilePosition, IProtoObjectStructure protoStructure, ICharacter byCharacter) { if (protoStructure == null) { throw new ArgumentNullException(nameof(protoStructure)); } var protoConstructionSite = protoStructure.ConstructionSitePrototype; var constructionSite = Server.World.CreateStaticWorldObject(protoConstructionSite, tilePosition); var serverState = ProtoObjectConstructionSite.GetPublicState(constructionSite); serverState.Setup(protoStructure); // reinitialize to build proper physics and occupy proper layout constructionSite.ServerRebuildScopeAndPhysics(); Logger.Important("Construction site created: " + constructionSite); protoConstructionSite.ServerOnBuilt(constructionSite, byCharacter); Api.SafeInvoke(() => ServerStructureBuilt?.Invoke(byCharacter, constructionSite)); Api.SafeInvoke(() => SharedWallConstructionRefreshHelper.SharedRefreshNeighborObjects( constructionSite, isDestroy: false)); return(constructionSite); }
public static void SharedRefreshNeighborObjects(IStaticWorldObject staticWorldObject, bool isDestroy) { var protoWorldObject = staticWorldObject.ProtoStaticWorldObject; switch (protoWorldObject) { case IProtoObjectWall _: case IProtoObjectDoor _: case ObjectWallDestroyed _: SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy); break; case ProtoObjectConstructionSite _: { var constructionProto = ProtoObjectConstructionSite.GetPublicState(staticWorldObject) .ConstructionProto; switch (constructionProto) { case IProtoObjectWall _: case IProtoObjectDoor _: SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy); break; } break; } } }
private static bool ClientValidateCanBuildCallback(Vector2Ushort tilePosition, bool logErrors) { var protoStructure = currentSelectedProtoConstruction; if (Client.World.GetTile(tilePosition) .StaticObjects .Any(so => so.ProtoStaticWorldObject == protoStructure || ProtoObjectConstructionSite.SharedIsConstructionOf(so, protoStructure))) { // this building is already built here return(false); } if (!protoStructure.CheckTileRequirements( tilePosition, Client.Characters.CurrentPlayerCharacter, logErrors: logErrors)) { // time requirements are not valid return(false); } var configBuild = protoStructure.ConfigBuild; if (configBuild.CheckStageCanBeBuilt(Client.Characters.CurrentPlayerCharacter)) { return(true); } //NotificationSystem.ClientShowNotification("Cannot build", "Not enough items.", NotificationColor.Bad); // close construction menu ClientCloseConstructionMenu(); return(false); }
public static void SharedRefreshNeighborObjects(IStaticWorldObject staticWorldObject, bool isDestroy) { if (isProcessingQueueNow) { // don't allow recursive refreshing return; } var protoWorldObject = staticWorldObject.ProtoStaticWorldObject; switch (protoWorldObject) { case IProtoObjectWall _: case IProtoObjectDoor _: case ObjectWallDestroyed _: SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy); break; case ProtoObjectConstructionSite _: { var constructionProto = ProtoObjectConstructionSite.GetPublicState(staticWorldObject) .ConstructionProto; switch (constructionProto) { case IProtoObjectWall _: case IProtoObjectDoor _: SharedRefreshNeighborObjects(staticWorldObject.OccupiedTile, isDestroy); break; } break; } } }
public static IComponentAttachedControl CreateAndAttach(IStaticWorldObject worldObject) { var control = new ConstructionOrRepairRequirementsTooltip(); control.WorldObject = worldObject; if (worldObject.ProtoStaticWorldObject is ProtoObjectConstructionSite) { // construction control.Setup( constructionSitePublicState: ProtoObjectConstructionSite.GetPublicState(worldObject)); } else { // repair control.Setup( objectToRepairPublicState: worldObject.GetPublicState <StaticObjectPublicState>()); } var centerOffset = worldObject.ProtoStaticWorldObject.SharedGetObjectCenterWorldOffset(worldObject); return(Api.Client.UI.AttachControl( worldObject, control, positionOffset: centerOffset + (0, -0.6), isFocusable: true)); }
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; }
private void UpdateBlueprintCanBuild(Tile tile) { if (this.HideBlueprintOnOverlapWithTheSameObject) { foreach (var tileObj in tile.StaticObjects) { if (tileObj.ProtoStaticWorldObject == this.protoStaticWorldObject || ProtoObjectConstructionSite.SharedIsConstructionOf(tileObj, this.protoStaticWorldObject)) { this.blueprintRenderer.IsEnabled = false; this.tilesBlueprint.IsEnabled = false; return; } } } this.blueprintRenderer.IsEnabled = true; this.tilesBlueprint.IsEnabled = true; var tilePosition = tile.Position; this.validateCanBuildCallback(tilePosition, logErrors: false, out var errorMessage, out var canPlace, out var isTooFar); if (this.blueprintRenderer is null) { // this component have been disabled during the validation callback return; } if (this.blueprintRenderer.IsCanBuild != canPlace || this.blueprintRenderer.IsTooFar != isTooFar) { this.isCanBuildChanged = true; } this.blueprintRenderer.IsCanBuild = canPlace; this.tilesBlueprint.IsCanBuild = canPlace; this.blueprintRenderer.IsTooFar = isTooFar; if (isTooFar && string.IsNullOrEmpty(errorMessage)) { errorMessage = CoreStrings.Notification_TooFar; } this.blueprintRenderer.CannotBuildReason = errorMessage; this.blueprintRenderer.RefreshMaterial(); // cache check result for 0.1 second this.cachedTimeRemainsSeconds = 0.1; }
/// <summary> /// Checks if the wall in other tile is compatible with this type. /// </summary> private static bool SharedIsCompatibleWallType( Tile tile, bool recognizeDestroyedWalls, bool recognizeConstructionSites, bool isHorizontal) { foreach (var worldObject in tile.StaticObjects) { if (worldObject.IsDestroyed) { continue; } if (worldObject.ProtoWorldObject is IProtoObjectWall || recognizeDestroyedWalls && worldObject.ProtoWorldObject is ObjectWallDestroyed) { return(true); } if (recognizeConstructionSites && ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectWall))) { return(true); } if (isHorizontal) { if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: true)) { return(true); } } // Disabled - no need for this as we cover that empty space with the hitboxes // and the space is too small to pass through so absence of a physical collider is fine. //else if (Api.IsServer) //{ // // Only on the server side we do the check for the vertical compatible door. // // That's because we don't want to draw the "compatible wall adapters" on the client side. // // It also means that physics is a bit different between the client and the server // // in this aspect but it's barely noticeable. // if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: false)) // { // return true; // } //} } return(false); }
/// <summary> /// Checks if the wall in other tile is compatible with this type. /// </summary> private static bool SharedIsCompatibleWallType( Tile tile, bool recognizeDestroyedWalls, bool recognizeConstructionSites, bool isHorizontal) { foreach (var worldObject in tile.StaticObjects) { if (worldObject.IsDestroyed) { continue; } if (worldObject.ProtoWorldObject is IProtoObjectWall || recognizeDestroyedWalls && worldObject.ProtoWorldObject is ObjectWallDestroyed) { return(true); } if (recognizeConstructionSites && ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectWall))) { return(true); } if (isHorizontal) { if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: true)) { return(true); } } // TODO: restore this. Commented out as it's preventing from moving via diagonal doors. //else if (Api.IsServer) //{ // // Only on the server side we do the check for the vertical compatible door. // // That's because we don't want to draw the "compatible wall adapters" on the client side. // // It also means that physics is a bit different between the client and the server // // in this aspect but it's barely noticeable. // if (SharedIsCompatibleDoor(worldObject, tile, recognizeConstructionSites, isHorizontal: false)) // { // return true; // } //} } return(false); }
private void UpdateBlueprintCanBuild(Tile tile) { foreach (var tileObj in tile.StaticObjects) { if (tileObj.ProtoStaticWorldObject == this.protoStaticWorldObject || ProtoObjectConstructionSite.SharedIsConstructionOf(tileObj, this.protoStaticWorldObject)) { this.blueprintRenderer.IsEnabled = false; this.tilesBlueprint.IsEnabled = false; return; } } this.blueprintRenderer.IsEnabled = true; this.tilesBlueprint.IsEnabled = true; var tilePosition = tile.Position; this.validateCanBuildCallback(tilePosition, logErrors: false, out var canPlace, out var isTooFar); if (this.blueprintRenderer == null) { // this component have been disabled during the validation callback return; } if (this.blueprintRenderer.IsCanBuild != canPlace || this.blueprintRenderer.IsTooFar != isTooFar) { this.isCanBuildChanged = true; } this.blueprintRenderer.IsCanBuild = canPlace; this.tilesBlueprint.IsCanBuild = canPlace; this.blueprintRenderer.IsTooFar = isTooFar; this.blueprintRenderer.RefreshMaterial(); // cache check result for 0.1 second this.cachedTimeRemainsSeconds = 0.1d; }
private static bool SharedIsCompatibleDoor( IStaticWorldObject worldObject, Tile tile, bool isConsiderConstructionSites, bool isHorizontal) { if (worldObject.ProtoWorldObject is IProtoObjectDoor && isHorizontal == worldObject.GetPublicState <ObjectDoorPublicState>().IsHorizontalDoor) { return(true); } if (isConsiderConstructionSites && ProtoObjectConstructionSite.SharedIsConstructionOf(worldObject, typeof(IProtoObjectDoor)) && isHorizontal == DoorHelper.IsHorizontalDoorNeeded(tile, checkExistingDoor: true)) { return(true); } return(false); }
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); }
public static bool CheckCanInteractForConstruction( ICharacter character, IStaticWorldObject worldObject, bool writeToLog, bool checkRaidblock) { var characterPosition = character.Position; var canInteract = false; var staticWorldObjectProto = ProtoObjectConstructionSite.SharedGetConstructionProto(worldObject) ?? worldObject.ProtoStaticWorldObject; var startTilePosition = worldObject.TilePosition; foreach (var tileOffset in staticWorldObjectProto.Layout.TileOffsets) { var tilePosition = startTilePosition + tileOffset; if (characterPosition.DistanceSquaredTo( new Vector2D(tilePosition.X + 0.5, tilePosition.Y + 0.5)) <= MaxDistanceForBuildRepairAction * MaxDistanceForBuildRepairAction) { canInteract = true; break; } } if (!canInteract) { canInteract = CreativeModeSystem.SharedIsInCreativeMode(character); } if (!canInteract) { if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} for (de)construction - too far.", character); if (IsClient) { CannotInteractMessageDisplay.ClientOnCannotInteract(worldObject, CoreStrings.Notification_TooFar, isOutOfRange: true); } } return(false); } if (checkRaidblock && LandClaimSystem.SharedIsUnderRaidBlock(character, worldObject)) { // the building is in an area under the raid if (writeToLog) { LandClaimSystem.SharedSendNotificationActionForbiddenUnderRaidblock(character); } return(false); } return(true); }
/// <summary> /// Find neighbor objects of type wall or construction site (for wall) and refresh their renderers /// according to their neighbors. /// </summary> public static void SharedRefreshNeighborObjects(Tile tile, bool isDestroy) { if (isRefreshing) { // don't allow recursive refreshing return; } try { isRefreshing = true; foreach (var neighborTile in tile.EightNeighborTiles) { foreach (var obj in neighborTile.StaticObjects) { var protoWorldObject = obj.ProtoWorldObject; switch (protoWorldObject) { case IProtoObjectWall _: case IProtoObjectDoor _: case ObjectWallDestroyed _: RefreshWorldObject(); break; case ProtoObjectConstructionSite _: { var constructionProto = ProtoObjectConstructionSite.GetPublicState(obj) .ConstructionProto; switch (constructionProto) { case IProtoObjectWall _: case IProtoObjectDoor _: RefreshWorldObject(); break; } break; } } // helper local function void RefreshWorldObject() { if (Api.IsClient) { obj.ClientInitialize(); } else { obj.ServerInitialize(); } } } } } finally { isRefreshing = false; } }