public static CharacterDamageContext Create( ICharacter attackerCharacter, ICharacter damagedCharacter, ProtoSkillWeapons protoWeaponSkill) { return(Current = new CharacterDamageContext(attackerCharacter, damagedCharacter, protoWeaponSkill)); }
public void Dispose() { Current = default; }
private static void SharedShotWeaponHitscan( ICharacter character, IProtoItemWeapon protoWeapon, Vector2D fromPosition, WeaponFinalCache weaponCache, Vector2D?customTargetPosition, IProtoCharacterCore characterProtoCharacter, double fireSpreadAngleOffsetDeg, CollisionGroup collisionGroup, bool isMeleeWeapon, IDynamicWorldObject characterCurrentVehicle, ProtoSkillWeapons protoWeaponSkill, PlayerCharacterSkills playerCharacterSkills, ITempList <IWorldObject> allHitObjects) { Vector2D toPosition; var rangeMax = weaponCache.RangeMax; if (customTargetPosition.HasValue) { var direction = customTargetPosition.Value - fromPosition; // ensure the max range is not exceeded direction = direction.ClampMagnitude(rangeMax); toPosition = fromPosition + direction; } else { toPosition = fromPosition + new Vector2D(rangeMax, 0) .RotateRad(characterProtoCharacter.SharedGetRotationAngleRad(character) + fireSpreadAngleOffsetDeg * Math.PI / 180.0); } using var lineTestResults = character.PhysicsBody.PhysicsSpace.TestLine( fromPosition: fromPosition, toPosition: toPosition, collisionGroup: collisionGroup); var damageMultiplier = 1d; var hitObjects = new List <WeaponHitData>(isMeleeWeapon ? 1 : lineTestResults.Count); var characterTileHeight = character.Tile.Height; if (IsClient || Api.IsEditor) { SharedEditorPhysicsDebugger.SharedVisualizeTestResults(lineTestResults, collisionGroup); } var isDamageRayStopped = false; foreach (var testResult in lineTestResults.AsList()) { var testResultPhysicsBody = testResult.PhysicsBody; var attackedProtoTile = testResultPhysicsBody.AssociatedProtoTile; if (attackedProtoTile != null) { if (attackedProtoTile.Kind != TileKind.Solid) { // non-solid obstacle - skip continue; } var attackedTile = IsServer ? Server.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position) : Client.World.GetTile((Vector2Ushort)testResultPhysicsBody.Position); if (attackedTile.Height < characterTileHeight) { // attacked tile is below - ignore it continue; } // tile on the way - blocking damage ray isDamageRayStopped = true; var hitData = new WeaponHitData(testResult.PhysicsBody.Position + SharedOffsetHitWorldPositionCloserToTileHitboxCenter( testResultPhysicsBody, testResult.Penetration, isRangedWeapon: !isMeleeWeapon)); hitObjects.Add(hitData); weaponCache.ProtoWeapon .SharedOnHit(weaponCache, null, 0, hitData, out _); break; } var damagedObject = testResultPhysicsBody.AssociatedWorldObject; if (ReferenceEquals(damagedObject, character) || ReferenceEquals(damagedObject, characterCurrentVehicle)) { // ignore collision with self continue; } if (!(damagedObject.ProtoGameObject is IDamageableProtoWorldObject damageableProto)) { // shoot through this object continue; } // don't allow damage is there is no direct line of sight on physical colliders layer between the two objects if (SharedHasTileObstacle(character.Position, characterTileHeight, damagedObject, targetPosition: testResult.PhysicsBody.Position + testResult.PhysicsBody.CenterOffset)) { continue; } using (CharacterDamageContext.Create(attackerCharacter: character, damagedObject as ICharacter, protoWeaponSkill)) { if (!damageableProto.SharedOnDamage( weaponCache, damagedObject, damageMultiplier, damagePostMultiplier: 1.0, out var obstacleBlockDamageCoef, out var damageApplied)) { // not hit continue; } var hitData = new WeaponHitData(damagedObject, testResult.Penetration.ToVector2F()); weaponCache.ProtoWeapon .SharedOnHit(weaponCache, damagedObject, damageApplied, hitData, out var isDamageStop); if (isDamageStop) { obstacleBlockDamageCoef = 1; } if (IsServer) { if (damageApplied > 0 && damagedObject is ICharacter damagedCharacter) { CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(damagedCharacter); } if (damageApplied > 0) { // give experience for damage protoWeaponSkill?.ServerOnDamageApplied(playerCharacterSkills, damagedObject, damageApplied); } } if (obstacleBlockDamageCoef < 0 || obstacleBlockDamageCoef > 1) { Logger.Error( "Obstacle block damage coefficient should be >= 0 and <= 1 - wrong calculation by " + damageableProto); break; } hitObjects.Add(hitData); if (isMeleeWeapon) { // currently melee weapon could attack only one object on the ray isDamageRayStopped = true; break; } damageMultiplier *= 1.0 - obstacleBlockDamageCoef; if (damageMultiplier <= 0) { // target blocked the damage ray isDamageRayStopped = true; break; } } } var shotEndPosition = GetShotEndPosition(isDamageRayStopped, hitObjects, toPosition, isRangedWeapon: !isMeleeWeapon); if (hitObjects.Count == 0) { protoWeapon.SharedOnMiss(weaponCache, shotEndPosition); } SharedCallOnWeaponHitOrTrace(character, protoWeapon, weaponCache.ProtoAmmo, shotEndPosition, hitObjects, endsWithHit: isDamageRayStopped); foreach (var entry in hitObjects) { if (!entry.IsCliffsHit && !allHitObjects.Contains(entry.WorldObject)) { allHitObjects.Add(entry.WorldObject); } } }