private static void SharedFireWeapon( ICharacter character, IItem weaponItem, IProtoItemWeapon protoWeapon, WeaponState weaponState) { protoWeapon.SharedOnFire(character, weaponState); var playerCharacterSkills = character.SharedGetSkills(); var protoWeaponSkill = playerCharacterSkills != null ? protoWeapon.WeaponSkillProto : null; if (IsServer) { // give experience for shot protoWeaponSkill?.ServerOnShot(playerCharacterSkills); } var weaponCache = weaponState.WeaponCache; if (weaponCache == null) { // calculate new weapon cache RebuildWeaponCache(character, weaponState); weaponCache = weaponState.WeaponCache; } // raycast possible victims var fromPosition = character.Position + (0, character.ProtoCharacter.CharacterWorldWeaponOffset); var toPosition = fromPosition + new Vector2D(weaponCache.RangeMax, 0) .RotateRad(character.ProtoCharacter.SharedGetRotationAngleRad(character)); var collisionGroup = protoWeapon is IProtoItemWeaponMelee ? CollisionGroups.HitboxMelee : CollisionGroups.HitboxRanged; using (var lineTestResults = character.PhysicsBody.PhysicsSpace.TestLine( fromPosition: fromPosition, toPosition: toPosition, collisionGroup: collisionGroup)) { var damageMultiplier = 1d; var isMeleeWeapon = protoWeapon is IProtoItemWeaponMelee; var hitObjects = new List <WeaponHitData>(isMeleeWeapon ? 1 : lineTestResults.Count); foreach (var testResult in lineTestResults) { var testResultPhysicsBody = testResult.PhysicsBody; var attackedProtoTile = testResultPhysicsBody.AssociatedProtoTile; if (attackedProtoTile != null) { if (attackedProtoTile.Kind != TileKind.Solid) { // non-solid obstacle - skip continue; } // tile on the way - blocking damage ray break; } var damagedObject = testResultPhysicsBody.AssociatedWorldObject; if (damagedObject == character) { // ignore collision with self continue; } if (!(damagedObject.ProtoGameObject is IDamageableProtoWorldObject damageableProto)) { // shoot through this object continue; } if (!damageableProto.SharedOnDamage( weaponCache, damagedObject, damageMultiplier, out var obstacleBlockDamageCoef, out var damageApplied)) { // not hit continue; } if (IsServer) { weaponCache.ProtoWeapon .ServerOnDamageApplied(weaponCache.Weapon, character, damagedObject, damageApplied); if (damageApplied > 0 && protoWeaponSkill != null) { // give experience for damage protoWeaponSkill.ServerOnDamageApplied(playerCharacterSkills, damagedObject, damageApplied); if (damagedObject is ICharacter damagedCharacter && damagedCharacter.GetPublicState <ICharacterPublicState>().CurrentStats.HealthCurrent <= 0) { // give weapon experience for kill Logger.Info("Killed " + damagedCharacter, character); protoWeaponSkill.ServerOnKill(playerCharacterSkills, killedCharacter: damagedCharacter); if (damagedCharacter.ProtoCharacter is ProtoCharacterMob protoMob) { // give hunting skill experience for mob kill var experience = SkillHunting.ExperienceForKill; experience *= protoMob.MobKillExperienceMultiplier; if (experience > 0) { playerCharacterSkills.ServerAddSkillExperience <SkillHunting>(experience); } } } } } if (obstacleBlockDamageCoef < 0 || obstacleBlockDamageCoef > 1) { Logger.Error( "Obstacle block damage coefficient should be >= 0 and <= 1 - wrong calculation by " + damageableProto); break; } //var hitPosition = testResultPhysicsBody.Position + testResult.Penetration; hitObjects.Add(new WeaponHitData(damagedObject)); //, hitPosition)); if (isMeleeWeapon) { // currently melee weapon could attack only one object on the ray break; } damageMultiplier = damageMultiplier * (1.0 - obstacleBlockDamageCoef); if (damageMultiplier <= 0) { // target blocked the damage ray break; } } if (hitObjects.Count > 0) { if (IsClient) { // display weapon shot on Client-side WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, hitObjects); } else // if server { // display damages on clients in scope of every damaged object using (var scopedBy = Api.Shared.GetTempList <ICharacter>()) { foreach (var hitObject in hitObjects) { if (hitObject.WorldObject.IsDestroyed) { continue; } Server.World.GetScopedByPlayers(hitObject.WorldObject, scopedBy); scopedBy.Remove(character); if (scopedBy.Count == 0) { continue; } Instance.CallClient(scopedBy, _ => _.ClientRemote_OnWeaponHit(protoWeapon, hitObject)); scopedBy.Clear(); } } } } if (IsServer) { protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, hitObjects); } } }
private static void SharedFireWeapon( ICharacter character, IItem weaponItem, IProtoItemWeapon protoWeapon, WeaponState weaponState) { if (!protoWeapon.SharedOnFire(character, weaponState)) { return; } var playerCharacterSkills = character.SharedGetSkills(); var protoWeaponSkill = playerCharacterSkills != null ? protoWeapon.WeaponSkillProto : null; if (IsServer) { protoWeaponSkill?.ServerOnShot(playerCharacterSkills); // give experience for shot CharacterUnstuckSystem.ServerTryCancelUnstuckRequest(character); } var weaponCache = weaponState.WeaponCache; if (weaponCache is null) { SharedRebuildWeaponCache(character, weaponState); weaponCache = weaponState.WeaponCache; } var characterCurrentVehicle = character.IsNpc ? null : character.SharedGetCurrentVehicle(); var isMeleeWeapon = protoWeapon is IProtoItemWeaponMelee; var characterProtoCharacter = (IProtoCharacterCore)character.ProtoCharacter; var fromPosition = characterProtoCharacter.SharedGetWeaponFireWorldPosition(character, isMeleeWeapon); var fireSpreadAngleOffsetDeg = protoWeapon.SharedUpdateAndGetFirePatternCurrentSpreadAngleDeg(weaponState); var collisionGroup = protoWeapon.CollisionGroup; using var allHitObjects = Shared.GetTempList <IWorldObject>(); var shotsPerFire = weaponCache.FireScatterPreset.ProjectileAngleOffets; foreach (var angleOffsetDeg in shotsPerFire) { SharedShotWeaponHitscan(character, protoWeapon, fromPosition, weaponCache, weaponState.CustomTargetPosition, characterProtoCharacter, fireSpreadAngleOffsetDeg + angleOffsetDeg, collisionGroup, isMeleeWeapon, characterCurrentVehicle, protoWeaponSkill, playerCharacterSkills, allHitObjects); } if (IsServer) { protoWeapon.ServerOnShot(character, weaponItem, protoWeapon, allHitObjects.AsList()); } }