private static void SharedCallOnWeaponFinished(WeaponState state, ICharacter character) { if (IsServer) { ServerCheckFiredShotsMismatch(state, character); } state.IsEventWeaponStartSent = false; if (IsClient) { // finished firing weapon on Client-side WeaponSystemClientDisplay.OnWeaponFinished(character); } else // if this is Server { // notify other clients about finished firing weapon using (var scopedBy = Api.Shared.GetTempList <ICharacter>()) { Server.World.GetScopedByPlayers(character, scopedBy); Instance.CallClient( scopedBy, _ => _.ClientRemote_OnWeaponFinished(character)); } } }
private void ClientRemote_OnWeaponHit(IProtoItemWeapon protoWeapon, WeaponHitData hitObject) { using (var tempList = Api.Shared.WrapObjectInTempList(hitObject)) { WeaponSystemClientDisplay.OnWeaponHit(protoWeapon, tempList); } }
private static void SharedCallOnWeaponShot(ICharacter character, IProtoItemWeapon protoWeapon) { if (IsClient) { // start firing weapon on Client-side WeaponSystemClientDisplay.OnWeaponShot(character, protoWeapon: protoWeapon, fallbackProtoCharacter: character.ProtoCharacter, fallbackPosition: character.Position); } else // if IsServer { using var observers = Api.Shared.GetTempList <ICharacter>(); var eventNetworkRadius = (byte)Math.Max( 15, Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max)); Server.World.GetCharactersInRadius(character.TilePosition, observers, radius: eventNetworkRadius, onlyPlayers: true); observers.Remove(character); Instance.CallClient(observers, _ => _.ClientRemote_OnWeaponShot(character, protoWeapon, character.ProtoCharacter, character.Position)); } }
private void ClientRemote_OnWeaponStart(ICharacter whoFires) { if (whoFires == null || !whoFires.IsInitialized) { return; } WeaponSystemClientDisplay.OnWeaponStart(whoFires); }
private static void ClientOnCharactersHitByExplosion(IReadOnlyList <WeaponHitData> hitCharacters) { foreach (var hitData in hitCharacters) { var protoWorldObject = hitData.FallbackProtoWorldObject; var objectMaterial = hitData.FallbackObjectMaterial; var hitWorldObject = hitData.WorldObject; if (hitWorldObject is not null && !hitWorldObject.IsInitialized) { hitWorldObject = null; } var worldObjectPosition = CalculateWorldObjectPosition(hitWorldObject, hitData); // apply some volume variation var volume = SoundConstants.VolumeHit; volume *= RandomHelper.Range(0.8f, 1.0f); var pitch = RandomHelper.Range(0.95f, 1.05f); if (hitWorldObject is not null) { SoundPresetHitExplosion.PlaySound( objectMaterial, hitWorldObject, volume: volume, pitch: pitch); } else { SoundPresetHitExplosion.PlaySound( objectMaterial, protoWorldObject, worldPosition: worldObjectPosition, volume: volume, pitch: pitch); } WeaponSystemClientDisplay.ClientAddHitSparks( ExplosionHitSparksPreset, hitData, hitWorldObject, protoWorldObject, worldObjectPosition, projectilesCount: 1, objectMaterial: objectMaterial, randomizeHitPointOffset: true, rotationAngleRad: null, randomRotation: true, drawOrder: DrawOrder.Light); }
private static void SharedCallOnWeaponShot(ICharacter character) { if (IsClient) { // start firing weapon on Client-side WeaponSystemClientDisplay.OnWeaponShot(character); } else // if IsServer { using (var scopedBy = Api.Shared.GetTempList <ICharacter>()) { Server.World.GetScopedByPlayers(character, scopedBy); Instance.CallClient(scopedBy, _ => _.ClientRemote_OnWeaponShot(character)); } } }
private void ClientRemote_OnWeaponShot( ICharacter whoFires, IProtoItemWeapon protoWeapon, IProtoCharacter fallbackProtoCharacter, Vector2D fallbackPosition) { if (whoFires != null && !whoFires.IsInitialized) { whoFires = null; } WeaponSystemClientDisplay.OnWeaponShot(whoFires, protoWeapon, fallbackProtoCharacter, fallbackPosition); }
private static void SharedCallOnWeaponStart(WeaponState state, ICharacter character) { Api.Assert(!state.IsEventWeaponStartSent, "Firing event must be not set"); state.IsEventWeaponStartSent = true; if (IsClient) { // start firing weapon on Client-side WeaponSystemClientDisplay.OnWeaponStart(character); } else // if IsServer { using var scopedBy = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(character, scopedBy); Instance.CallClient(scopedBy, _ => _.ClientRemote_OnWeaponStart(character)); } }
private void ClientRemote_OnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, IProtoCharacter protoCharacter, Vector2Ushort fallbackCharacterPosition, WeaponHitData[] hitObjects, Vector2D endPosition, bool endsWithHit) { WeaponSystemClientDisplay.ClientOnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, protoCharacter, fallbackCharacterPosition, hitObjects, endPosition, endsWithHit); }
private static void SharedCallOnWeaponInputStop(WeaponState state, ICharacter character) { Api.Assert(state.IsEventWeaponStartSent, "Firing event must be set"); state.IsEventWeaponStartSent = false; if (IsClient) { // finished firing weapon on Client-side WeaponSystemClientDisplay.OnWeaponInputStop(character); } else // if this is Server { // notify other clients about finished firing weapon using var scopedBy = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(character, scopedBy); Instance.CallClient( scopedBy, _ => _.ClientRemote_OnWeaponInputStop(character)); } }
private static void SharedCallOnWeaponShot( ICharacter character, IProtoItemWeapon protoWeapon) { if (IsClient) { // start firing weapon on Client-side WeaponSystemClientDisplay.ClientOnWeaponShot(character, partyId: 0, // not relevant here as it's the current player firing the weapon protoWeapon: protoWeapon, protoCharacter: character.ProtoCharacter, fallbackPosition: character.Position.ToVector2Ushort()); } else // if IsServer { using var observers = Shared.GetTempList <ICharacter>(); var eventNetworkRadius = (byte)Math.Max( 15, Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max)); Server.World.GetCharactersInRadius(character.TilePosition, observers, radius: eventNetworkRadius, onlyPlayers: true); observers.Remove(character); if (observers.Count > 0) { var partyId = PartySystem.ServerGetParty(character)?.Id ?? 0; Instance.CallClient(observers.AsList(), _ => _.ClientRemote_OnWeaponShot(character, partyId, protoWeapon, character.ProtoCharacter, character.Position.ToVector2Ushort())); } } }
private void ClientRemote_OnWeaponStart(ICharacter whoFires) { WeaponSystemClientDisplay.OnWeaponStart(whoFires); }
private void ClientRemote_OnWeaponInputStop(ICharacter whoFires) { WeaponSystemClientDisplay.OnWeaponInputStop(whoFires); }
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); } } }
public static void Create( WeaponFireTracePreset weaponTracePreset, Vector2D worldPositionSource, Vector2D endPosition, WeaponHitData lastHitData, bool hasHit) { if (weaponTracePreset is null) { // no weapon trace for this weapon return; } var deltaPos = endPosition - worldPositionSource; var fireDistance = CalculateFireDistance(weaponTracePreset, deltaPos); if (fireDistance <= weaponTracePreset.TraceMinDistance) { return; } CalculateAngleAndDirection(deltaPos, out var angleRad, out var normalizedRay); // offset start position of the ray worldPositionSource += normalizedRay * weaponTracePreset.TraceStartWorldOffset; // actual trace life duration is larger when has a hit // (to provide a contact fade-out animation for the sprite length) if (!hasHit) { // otherwise it's shorter on the sprite length fireDistance -= weaponTracePreset.TraceWorldLength; } var totalDuration = WeaponSystemClientDisplay.SharedCalculateTimeToHit(fireDistance, weaponTracePreset); var sceneObject = Api.Client.Scene.CreateSceneObject("Temp_WeaponTrace"); var componentSpriteRender = Api.Client.Rendering.CreateSpriteRenderer( sceneObject, weaponTracePreset.TraceTexture, positionOffset: Vector2D.Zero, spritePivotPoint: (0, 0.5), // yes, it's actually making the weapon trace to draw in the light layer! drawOrder: DrawOrder.Light); componentSpriteRender.RotationAngleRad = (float)angleRad; componentSpriteRender.BlendMode = weaponTracePreset.UseScreenBlending ? BlendMode.Screen // it's important to use premultiplied mode here for correct rendering : BlendMode.AlphaBlendPremultiplied; sceneObject.AddComponent <ComponentWeaponTrace>() .Setup(weaponTracePreset, componentSpriteRender, worldPositionSource, normalizedRay, fireDistance, totalDuration, hasHit, lastHitData); sceneObject.Destroy(totalDuration); }
private static void SharedCallOnWeaponHitOrTrace( ICharacter firingCharacter, IProtoItemWeapon protoWeapon, IProtoItemAmmo protoAmmo, Vector2D endPosition, List <WeaponHitData> hitObjects, bool endsWithHit) { if (IsClient) { // display weapon shot on Client-side WeaponSystemClientDisplay.ClientOnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, firingCharacter.ProtoCharacter, firingCharacter.Position.ToVector2Ushort(), hitObjects, endPosition, endsWithHit); } else // if server { // display damages on clients in scope of every damaged object var observers = new HashSet <ICharacter>(); using var tempList = Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(firingCharacter, tempList); observers.AddRange(tempList.AsList()); foreach (var hitObject in hitObjects) { if (hitObject.IsCliffsHit || hitObject.WorldObject.IsDestroyed) { continue; } if (hitObject.WorldObject is ICharacter damagedCharacter && !damagedCharacter.IsNpc) { // notify the damaged character observers.Add(damagedCharacter); } Server.World.GetScopedByPlayers(hitObject.WorldObject, tempList); tempList.Clear(); observers.AddRange(tempList.AsList()); } // add all observers within the sound radius (so they can not only hear but also see the traces) var eventNetworkRadius = (byte)Math.Max( 15, Math.Ceiling(protoWeapon.SoundPresetWeaponDistance.max)); tempList.Clear(); Server.World.GetCharactersInRadius(firingCharacter.TilePosition, tempList, radius: eventNetworkRadius, onlyPlayers: true); observers.AddRange(tempList.AsList()); // don't notify the attacking character observers.Remove(firingCharacter); if (observers.Count > 0) { Instance.CallClient(observers, _ => _.ClientRemote_OnWeaponHitOrTrace(firingCharacter, protoWeapon, protoAmmo, firingCharacter.ProtoCharacter, firingCharacter .Position.ToVector2Ushort(), hitObjects.ToArray(), endPosition, endsWithHit)); } } }