protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IDynamicWorldObject targetObject, out double obstacleBlockDamageCoef) { if (PveSystem.SharedIsPve(false) && (weaponCache.Character is null || !weaponCache.Character.IsNpc)) { // no PvP damage in PvE (only creature damage is allowed) obstacleBlockDamageCoef = 0; return(0); } return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override void ServerOnStaticObjectDamageApplied( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, float previousStructurePoints, float currentStructurePoints) { var character = weaponCache.Character; if (character is not null && !(weaponCache.ProtoWeapon is IProtoItemWeaponRanged) && WorldObjectClaimSystem.SharedIsEnabled) { this.ServerTryClaimObject(targetObject, character); } base.ServerOnStaticObjectDamageApplied(weaponCache, targetObject, previousStructurePoints, currentStructurePoints); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IDynamicWorldObject targetObject, out double obstacleBlockDamageCoef) { var damage = base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef); if (weaponCache.Character?.ProtoGameObject is IProtoCharacterMob protoCharacterMob && protoCharacterMob.IsBoss) { // for balancing reasons we're increasing damage by boss damage *= 1.75; } return(damage); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (IsServer) { damagePreMultiplier = LandClaimSystem.ServerAdjustDamageToUnprotectedStrongBuilding(weaponCache, targetObject, damagePreMultiplier); } var damage = base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef); return(damage); }
protected virtual double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IDynamicWorldObject targetObject, out double obstacleBlockDamageCoef) { obstacleBlockDamageCoef = this.ObstacleBlockDamageCoef; if (IsClient) { // we don't apply any damage on the Client-side return(0); } return(WeaponDamageSystem.ServerCalculateTotalDamage( weaponCache, targetObject, this.DefenseStats, damagePreMultiplier, clampDefenseTo1: false)); }
public override void ServerExecuteExplosion( Vector2D positionEpicenter, IPhysicsSpace physicsSpace, WeaponFinalCache weaponFinalCache) { WeaponExplosionSystem.ServerProcessExplosionCircle( positionEpicenter: positionEpicenter, physicsSpace: physicsSpace, damageDistanceMax: this.DamageRadius, weaponFinalCache: weaponFinalCache, damageOnlyDynamicObjects: true, isDamageThroughObstacles: this.IsDamageThroughObstacles, callbackCalculateDamageCoefByDistanceForStaticObjects: this.ServerCalculateDamageCoefByDistanceForStaticObjects, callbackCalculateDamageCoefByDistanceForDynamicObjects: this .ServerCalculateDamageCoefByDistanceForDynamicObjects, // Missiles are falling from the sky and the explosion circles are clearly designated. // Players expecting that they will be not damaged when they stand outside the circles. collisionGroups: new[] { CollisionGroup.Default }); }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { if (weaponCache.Character != null) { // damaged by character if (IsServer) { // notify other characters using (var tempList = Api.Shared.GetTempList <ICharacter>()) { ServerWorld.GetScopedByPlayers(targetObject, tempList); tempList.Remove(weaponCache.Character); this.CallClient(tempList, _ => _.ClientRemote_OnHit()); } } else { ClientOnHit(); } } if (weaponCache.ProtoWeapon != null && !(weaponCache.ProtoWeapon is IProtoItemWeaponMelee)) { // hit but not damaged - only melee weapons (including pickaxes can damage this) obstacleBlockDamageCoef = this.ObstacleBlockDamageCoef; damageApplied = 0; return(true); } return(base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, out obstacleBlockDamageCoef, out damageApplied)); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (weaponCache.ProtoWeapon is IProtoItemToolMining protoItemToolMining) { // mining mineral with a mining device // block next damage completely - only one mineral could be mined at once obstacleBlockDamageCoef = 1; // get damage multiplier ("mining speed") var damageMultiplier = weaponCache.Character .SharedGetFinalStatMultiplier(StatName.MiningSpeed); return(protoItemToolMining.DamageToMinerals * damageMultiplier * ToolsConstants.ActionMiningSpeedMultiplier); } if (weaponCache.ProtoWeapon is ItemNoWeapon) { // no damage with hands obstacleBlockDamageCoef = 1; if (IsClient) { NotificationSystem.ClientShowNotification(NotificationUsePickaxe, icon: this.Icon); } return(0); } // not a mining tool - call default damage apply method return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (weaponCache.ProtoWeapon is ItemNoWeapon) { // no damage with hands obstacleBlockDamageCoef = 1; if (IsClient) { NotificationSystem.ClientShowNotification(NotificationUseWeaponOrTool, icon: this.Icon); } return(0); } if (!weaponCache.ProtoWeapon?.CanDamageStructures ?? false) { // probably a mob weapon obstacleBlockDamageCoef = 1; return(0); } if (IsServer && !(this is ProtoObjectConstructionSite)) { damagePreMultiplier = LandClaimSystem.ServerAdjustDamageToUnclaimedBuilding(weaponCache, targetObject, damagePreMultiplier); } return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override void ServerOnStaticObjectZeroStructurePoints( WeaponFinalCache weaponCache, ICharacter byCharacter, IWorldObject targetObject) { // do not use default implementation because it will destroy the object automatically //base.ServerOnStaticObjectZeroStructurePoints(weaponCache, targetObject); var worldObject = (IStaticWorldObject)targetObject; var publicState = GetPublicState(worldObject); if (byCharacter != null && (LandClaimSystem.ServerIsOwnedArea(publicState.LandClaimAreaObject, byCharacter) || CreativeModeSystem.SharedIsInCreativeMode(byCharacter))) { // this is the owner of the area or the player is in a creative mode if (byCharacter.SharedGetPlayerSelectedHotbarItemProto() is ProtoItemToolCrowbar) { publicState.ServerTimeForDestruction = 0; Logger.Important( $"Land claim object {targetObject} destroyed by the owner with a crowbar - no destruction timer", byCharacter); this.ServerForceUpdate(worldObject, publicState); return; } } if (publicState.ServerTimeForDestruction.HasValue) { // destruction timer is already set return; } // the land claim structure points is zero - it's broken now - set timer for destruction publicState.ServerTimeForDestruction = Server.Game.FrameTime + this.DestructionTimeout.TotalSeconds; Logger.Important($"Timer for destruction set: {targetObject}. Timeout: {this.DestructionTimeout}"); this.ServerForceUpdate(worldObject, publicState); }
public override void ServerOnObjectHitByExplosion( IWorldObject worldObject, double damage, WeaponFinalCache weaponCache) { base.ServerOnObjectHitByExplosion(worldObject, damage, weaponCache); if (worldObject is ICharacter character) { if (damage >= 5) { character.ServerAddStatusEffect <StatusEffectDazed>(intensity: 0.5); } } else if (worldObject is IDynamicWorldObject dynamicWorldObject && dynamicWorldObject.ProtoGameObject is IProtoVehicle protoVehicle) { // apply additional vehicle damage var vehicleDamage = damage * VehicleAdditionalDamageMultiplier; protoVehicle.ServerApplyDamage(dynamicWorldObject, vehicleDamage); } }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { if (weaponCache.ProtoObjectExplosive != null) { // accept explosive damage return(base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, out obstacleBlockDamageCoef, out damageApplied)); } // only damage from explosives is accepted obstacleBlockDamageCoef = 0; damageApplied = 0; // no damage return(false); // no hit }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { if (IsServer) { // an explosive object was damaged var privateState = GetPrivateState(targetObject); if (privateState.ExplosionDelaySecondsRemains > 0.1) { // explode soon! privateState.ExplosionDelaySecondsRemains = 0.1; } } obstacleBlockDamageCoef = 0; damageApplied = 0; // no damage return(false); // no hit }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { if (IsServer && (!weaponCache.Character?.IsNpc ?? false)) { var mobPrivateState = GetPrivateState((ICharacter)targetObject); mobPrivateState.CurrentAgroCharacter = weaponCache.Character; //Logger.Dev( // $"Mob damaged by player, let's agro: {targetObject} by {mobPrivateState.CurrentAgroCharacter}"); } return(base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, out obstacleBlockDamageCoef, out damageApplied)); }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { if (IsServer) { LandClaimSystem.ServerOnRaid(targetObject.Bounds, weaponCache.Character, isShort: this.IsShortRaidblockOnHit); } var publicState = GetPublicState(targetObject); var previousStructurePoints = publicState.StructurePointsCurrent; if (previousStructurePoints <= 0f) { // already destroyed land claim object (waiting for the destroy timer) obstacleBlockDamageCoef = this.ObstacleBlockDamageCoef; damageApplied = 0; if (IsServer && weaponCache.Character != null) { var areaPrivateState = LandClaimArea.GetPrivateState(publicState.LandClaimAreaObject); areaPrivateState.IsDestroyedByPlayers = true; } return(true); } return(base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, out obstacleBlockDamageCoef, out damageApplied)); }
protected override void ServerOnStaticObjectZeroStructurePoints( WeaponFinalCache weaponCache, ICharacter byCharacter, IWorldObject targetObject) { base.ServerOnStaticObjectZeroStructurePoints(weaponCache, byCharacter, targetObject); if (weaponCache == null || (weaponCache.ProtoWeapon == null && weaponCache.ProtoObjectExplosive == null) || PveSystem.ServerIsPvE) { return; } // the damage was dealt by a weapon or explosive - try to explode the deposit var worldObjectDeposit = this.SharedGetDepositWorldObject( Server.World.GetTile(targetObject.TilePosition)); ((IProtoObjectDeposit)worldObjectDeposit?.ProtoStaticWorldObject)? .ServerOnExtractorDestroyedForDeposit(worldObjectDeposit); }
public override void SharedOnHit( WeaponFinalCache weaponCache, IWorldObject damagedObject, double damage, WeaponHitData hitData, out bool isDamageStop) { if (damagedObject is ICharacter) { base.SharedOnHit(weaponCache, damagedObject, damage, hitData, out isDamageStop); } // make arrows to always stop on the first hit and have a chance to drop on the ground isDamageStop = true; if (IsServer && isDamageStop && RandomHelper.NextDouble() <= 2 / 3.0) // 2/3 chance to recover the arrow { this.ServerCreateDroppedArrow(weaponCache, hitData.FallbackTilePosition.ToVector2D() + hitData.HitPoint); } }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, double damagePreMultiplier, double damagePostMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { var result = base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, damagePostMultiplier, out obstacleBlockDamageCoef, out damageApplied); if (!result) { return(false); } if (IsClient) { return(true); } ObjectMineralPragmiumSource.ServerTryClaimPragmiumClusterNearCharacter(weaponCache.Character); return(true); }
public override void SharedOnHit( WeaponFinalCache weaponCache, IWorldObject damagedObject, double damage, WeaponHitData hitData, out bool isDamageStop) { base.SharedOnHit(weaponCache, damagedObject, damage, hitData, out isDamageStop); if (IsServer && damage > 0 && damagedObject is ICharacter damagedCharacter) { // guaranteed small toxin effect per hit // (please note the addition formula inside the Toxins effect class is NOT linear // so subsequent hits increasing it on less than 20%) damagedCharacter.ServerAddStatusEffect <StatusEffectToxins>(intensity: 0.2); } }
protected virtual void ServerOnStaticObjectZeroStructurePoints( [CanBeNull] WeaponFinalCache weaponCache, [CanBeNull] ICharacter byCharacter, [NotNull] IWorldObject targetObject) { if (targetObject.IsDestroyed) { return; } Logger.Info($"Static object destroyed: {targetObject} by {byCharacter}"); this.ServerSendObjectDestroyedEvent(targetObject); Server.World.DestroyObject(targetObject); if (weaponCache != null) { this.ServerOnStaticObjectDestroyedByCharacter( byCharacter, weaponCache.ProtoWeapon, (IStaticWorldObject)targetObject); } }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (NewbieProtectionSystem.SharedIsNewbie(weaponCache.Character)) { // don't allow mining a boss loot while under newbie protection if (IsClient) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(this); } obstacleBlockDamageCoef = 0; return(0); } return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (weaponCache.ProtoWeapon is IProtoItemToolWoodcutting protoItemToolWoodCutting) { obstacleBlockDamageCoef = 1; // get damage multiplier ("woodcutting speed") var damageMultiplier = weaponCache.Character .SharedGetFinalStatMultiplier(StatName.WoodcuttingSpeed); return(protoItemToolWoodCutting.DamageToTree * damageMultiplier); } if (weaponCache.ProtoWeapon is ItemNoWeapon) { // no damage with hands obstacleBlockDamageCoef = 1; if (IsClient) { NotificationSystem.ClientShowNotification(NotificationUseAxe, icon: this.Icon); } return(0); } // not a wood-cutting tool - call default damage apply method return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override void ServerOnStaticObjectZeroStructurePoints( WeaponFinalCache weaponCache, ICharacter byCharacter, IWorldObject targetObject) { base.ServerOnStaticObjectZeroStructurePoints(weaponCache, byCharacter, targetObject); if (weaponCache is null || (weaponCache.ProtoWeapon is null && weaponCache.ProtoExplosive is null) || PveSystem.ServerIsPvE) { return; } // explode var tilePosition = targetObject.TilePosition; Server.World.CreateStaticWorldObject <ObjectDepositExplosion>(tilePosition); // the damage was dealt by a weapon or explosive - try to explode the deposit var worldObjectDeposit = this.SharedGetDepositWorldObject( Server.World.GetTile(tilePosition)); if (worldObjectDeposit is not null) { ((IProtoObjectDeposit)worldObjectDeposit.ProtoStaticWorldObject) .ServerOnExtractorDestroyedForDeposit(worldObjectDeposit); } else { // create charred ground at the center of the explosion Server.World.CreateStaticWorldObject(GetProtoEntity <ObjectCharredGround1>(), (tilePosition + this.Layout.Center.ToVector2Int()) .ToVector2Ushort()); } }
public override void ServerExecuteExplosion( Vector2D positionEpicenter, IPhysicsSpace physicsSpace, WeaponFinalCache weaponFinalCache) { WeaponExplosionSystem.ServerProcessExplosionCircle( positionEpicenter: positionEpicenter, physicsSpace: physicsSpace, damageDistanceMax: this.DamageRadius, weaponFinalCache: weaponFinalCache, damageOnlyDynamicObjects: true, isDamageThroughObstacles: this.IsDamageThroughObstacles, callbackCalculateDamageCoefByDistanceForStaticObjects: this.ServerCalculateDamageCoefByDistanceForStaticObjects, callbackCalculateDamageCoefByDistanceForDynamicObjects: this .ServerCalculateDamageCoefByDistanceForDynamicObjects, // Missiles are falling from the sky and the explosion circles are clearly designated. // Players are expecting that they will be not damaged when they stand outside the circles. collisionGroups: new[] { CollisionGroups.Default }); // this is only for hoverboards as hoverboards on the ground have no physical collider WeaponExplosionSystem.ServerProcessExplosionCircle( positionEpicenter: positionEpicenter, physicsSpace: physicsSpace, damageDistanceMax: this.DamageRadius, weaponFinalCache: weaponFinalCache, damageOnlyDynamicObjects: true, isDamageThroughObstacles: this.IsDamageThroughObstacles, callbackCalculateDamageCoefByDistanceForStaticObjects: this.ServerCalculateDamageCoefByDistanceForStaticObjects, callbackCalculateDamageCoefByDistanceForDynamicObjects: this .ServerCalculateDamageCoefByDistanceForDynamicObjects, // this is only for hoverboards collisionGroups: new[] { CollisionGroups.HitboxMelee }, // can damage only hoverboards filterCanDamage: worldObject => worldObject.ProtoGameObject is IProtoVehicleHoverboard); }
protected override void ServerOnDynamicObjectZeroStructurePoints( WeaponFinalCache weaponCache, ICharacter byCharacter, IWorldObject targetObject) { var vehicle = (IDynamicWorldObject)targetObject; // explode using var scopedBy = Api.Shared.GetTempList <ICharacter>(); Server.World.GetCharactersInRadius(vehicle.TilePosition, scopedBy, radius: this.DestroyedExplosionRadius, onlyPlayers: true); this.CallClient(scopedBy.AsList(), _ => _.ClientRemote_VehicleExploded(vehicle.Position)); ExplosionHelper.ServerExplode( character: null, // yes, no damaging character here otherwise it will not receive the damage if staying close protoExplosive: null, protoWeapon: null, explosionPreset: this.DestroyedExplosionPreset, epicenterPosition: vehicle.Position + this.SharedGetObjectCenterWorldOffset(targetObject), damageDescriptionCharacters: this.DestroyedVehicleDamageDescriptionCharacters, physicsSpace: targetObject.PhysicsBody.PhysicsSpace, executeExplosionCallback: this.ServerExecuteVehicleExplosion); // destroy the vehicle completely after short explosion delay ServerTimersSystem.AddAction( this.DestroyedExplosionPreset.ServerDamageApplyDelay, () => base.ServerOnDynamicObjectZeroStructurePoints(weaponCache, byCharacter, targetObject)); }
protected virtual void ServerExecuteExplosion( Vector2D positionEpicenter, IPhysicsSpace physicsSpace, WeaponFinalCache weaponFinalCache) { WeaponExplosionSystem.ServerProcessExplosionCircle( positionEpicenter: positionEpicenter, physicsSpace: physicsSpace, damageDistanceMax: this.DamageRadius, weaponFinalCache: weaponFinalCache, damageOnlyDynamicObjects: false, isDamageThroughObstacles: false, callbackCalculateDamageCoefByDistanceForStaticObjects: CalcDamageCoefByDistance, callbackCalculateDamageCoefByDistanceForDynamicObjects: CalcDamageCoefByDistance, collisionGroup: CollisionGroups.HitboxRanged); double CalcDamageCoefByDistance(double distance) { var distanceThreshold = 0.5; if (distance <= distanceThreshold) { return(1); } distance -= distanceThreshold; distance = Math.Max(0, distance); var maxDistance = this.DamageRadius; maxDistance -= distanceThreshold; maxDistance = Math.Max(0, maxDistance); return(1 - Math.Min(distance / maxDistance, 1)); } }
public override void SharedOnHit(WeaponFinalCache weaponCache, IWorldObject damagedObject, double damage, WeaponHitData hitData, out bool isDamageStop) { weaponCache.AllowNpcToNpcDamage = true; base.SharedOnHit(weaponCache, damagedObject, damage, hitData, out isDamageStop); }
private void ServerOnMineralStageMined( WeaponFinalCache weaponCache, IStaticWorldObject mineralObject, int damageStage) { var byCharacter = weaponCache.Character; var byWeaponProto = weaponCache.ProtoWeapon; Logger.Info( $"{mineralObject} current damage stage changed to {damageStage}. Dropping items for that stage", byCharacter); try { var dropItemsList = this.DropItemsConfig.GetForStage(damageStage); var dropItemContext = new DropItemContext(byCharacter, mineralObject, byWeaponProto, weaponCache.ProtoExplosive); var probabilityMultiplier = this.ServerGetDropListProbabilityMultiplier(mineralObject); var objectDrone = weaponCache.Drone; if (objectDrone is not null) { // drop resources into the internal storage of the drone var storageItemsContainer = ((IProtoDrone)objectDrone.ProtoGameObject) .ServerGetStorageItemsContainer(objectDrone); dropItemsList.TryDropToContainer(storageItemsContainer, dropItemContext, probabilityMultiplier: probabilityMultiplier); } else if (byWeaponProto is IProtoItemWeaponMelee) { var result = dropItemsList.TryDropToCharacterOrGround( byCharacter, (mineralObject.TilePosition.ToVector2D() + this.Layout.Center).ToVector2Ushort(), dropItemContext, probabilityMultiplier: probabilityMultiplier, groundContainer: out _); if (result.TotalCreatedCount > 0) { NotificationSystem.ServerSendItemsNotification(byCharacter, result); } } else { // not a melee weapon or cannot drop to the character inventory - drop on the ground only dropItemsList.TryDropToGround( (mineralObject.TilePosition.ToVector2D() + this.Layout.Center).ToVector2Ushort(), dropItemContext, out _, probabilityMultiplier: probabilityMultiplier); } } finally { if (byWeaponProto is IProtoItemToolMining || weaponCache.ProtoExplosive is ObjectBombMining) { // add experience proportional to the mineral structure points (effectively - for the time spent on mining) var exp = SkillProspecting.ExperienceAddPerStructurePoint; exp *= this.StructurePointsMax / this.DamageStagesCount; byCharacter?.ServerAddSkillExperience <SkillProspecting>(exp); } this.ServerOnMineralStageMined(byCharacter, mineralObject); } }
public static void ServerExplode( [CanBeNull] ICharacter character, [CanBeNull] IProtoObjectExplosive protoObjectExplosive, ExplosionPreset explosionPreset, Vector2D epicenterPosition, DamageDescription damageDescriptionCharacters, IPhysicsSpace physicsSpace, ExecuteExplosionDelegate executeExplosionCallback) { ValidateIsServer(); // schedule explosion charred ground spawning ServerTimersSystem.AddAction( delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5, () => { var tilePosition = (Vector2Ushort)epicenterPosition; // remove existing charred ground objects at the same tile foreach (var staticWorldObject in Shared.WrapInTempList( Server.World.GetTile(tilePosition).StaticObjects)) { if (staticWorldObject.ProtoStaticWorldObject is ObjectCharredGround) { Server.World.DestroyObject(staticWorldObject); } } // spawn charred ground var objectCharredGround = Server.World .CreateStaticWorldObject <ObjectCharredGround>(tilePosition); var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D(); if (objectCharredGroundOffset != Vector2D.Zero) { ObjectCharredGround.ServerSetWorldOffset(objectCharredGround, (Vector2F)objectCharredGroundOffset); } }); // schedule explosion damage ServerTimersSystem.AddAction( delaySeconds: explosionPreset.ServerDamageApplyDelay, () => { // prepare weapon caches var characterFinalStatsCache = character?.SharedGetFinalStatsCache() ?? FinalStatsCache.Empty; var weaponFinalCache = new WeaponFinalCache(character, characterFinalStatsCache, weapon: null, protoWeapon: null, protoObjectExplosive: protoObjectExplosive, damageDescription: damageDescriptionCharacters); // execute explosion executeExplosionCallback( positionEpicenter: epicenterPosition, physicsSpace: physicsSpace, weaponFinalCache: weaponFinalCache); }); }
public sealed override void ClientOnMiss(WeaponFinalCache weaponCache, Vector2D endPosition) { this.ClientExplodeAt(weaponCache, endPosition); }