partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable <ISerializableEntity> targets, Hull hull, Vector2 worldPosition, bool playSound) { if (entity == null) { return; } if (sounds.Count > 0 && playSound) { if (soundChannel == null || !soundChannel.IsPlaying) { if (soundSelectionMode == SoundSelectionMode.All) { foreach (RoundSound sound in sounds) { if (sound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace(); GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } soundChannel = SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, hullGuess: hull); if (soundChannel != null) { soundChannel.Looping = loopSound; } } } else { int selectedSoundIndex; if (soundSelectionMode == SoundSelectionMode.ItemSpecific && entity is Item item) { selectedSoundIndex = item.ID % sounds.Count; } else if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && entity is Character user) { selectedSoundIndex = user.ID % sounds.Count; } else { selectedSoundIndex = Rand.Int(sounds.Count); } var selectedSound = sounds[selectedSoundIndex]; if (selectedSound?.Sound == null) { string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound?.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace(); GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } if (selectedSound.Sound.Disposed) { Submarine.ReloadRoundSound(selectedSound); } soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, worldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: hull); if (soundChannel != null) { soundChannel.Looping = loopSound; } } } else { soundChannel.Position = new Vector3(worldPosition, 0.0f); } if (soundChannel != null && soundChannel.Looping) { ActiveLoopingSounds.Add(this); soundEmitter = entity; loopStartTime = Timing.TotalTime; } } foreach (ParticleEmitter emitter in particleEmitters) { float angle = 0.0f; float particleRotation = 0.0f; if (emitter.Prefab.CopyEntityAngle) { Limb targetLimb = null; if (entity is Item item && item.body != null) { angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); particleRotation = -item.body.Rotation; if (item.body.Dir < 0.0f) { particleRotation += MathHelper.Pi; } } else if (entity is Character c && targetLimbs?.FirstOrDefault(l => l != LimbType.None) is LimbType l) { targetLimb = c.AnimController.GetLimb(l); }
void UpdateWalkAnim(float deltaTime) { if (CurrentGroundedParams == null) { return; } movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f); Collider.LinearVelocity = new Vector2( movement.X, Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y); //limbs are disabled when simple physics is enabled, no need to move them if (SimplePhysicsEnabled) { return; } float mainLimbHeight = ColliderHeightFromFloor; Vector2 colliderBottom = GetColliderBottom(); float movementAngle = 0.0f; float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle ?? 0 : HeadAngle ?? 0) * Dir; while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { movementAngle += MathHelper.TwoPi; } while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { movementAngle -= MathHelper.TwoPi; } Limb torso = GetLimb(LimbType.Torso); if (torso != null) { if (TorsoAngle.HasValue) { SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, MainLimb, TorsoTorque); } if (TorsoPosition.HasValue) { Vector2 pos = colliderBottom + Vector2.UnitY * TorsoPosition.Value; if (torso != MainLimb) { pos.X = torso.SimPosition.X; } else { mainLimbHeight = TorsoPosition.Value; } torso.MoveToPos(pos, TorsoMoveForce); torso.PullJointEnabled = true; torso.PullJointWorldAnchorB = pos; } } Limb head = GetLimb(LimbType.Head); if (head != null) { if (HeadAngle.HasValue) { SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, MainLimb, HeadTorque); } if (HeadPosition.HasValue) { Vector2 pos = colliderBottom + Vector2.UnitY * HeadPosition.Value; if (head != MainLimb) { pos.X = head.SimPosition.X; } else { mainLimbHeight = HeadPosition.Value; } head.MoveToPos(pos, HeadMoveForce); head.PullJointEnabled = true; head.PullJointWorldAnchorB = pos; } } if (TailAngle.HasValue) { var tail = GetLimb(LimbType.Tail); if (tail != null) { SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque); } } WalkPos -= MainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f); Vector2 transformedStepSize = Vector2.Zero; if (Math.Abs(TargetMovement.X) > 0.01f) { transformedStepSize = new Vector2( (float)Math.Cos(WalkPos) * StepSize.Value.X * 3.0f, (float)Math.Sin(WalkPos) * StepSize.Value.Y * 2.0f); } foreach (Limb limb in Limbs) { switch (limb.type) { case LimbType.LeftFoot: case LimbType.RightFoot: Vector2 footPos = new Vector2(limb.SimPosition.X, colliderBottom.Y); if (limb.RefJointIndex > -1) { if (LimbJoints.Length <= limb.RefJointIndex) { DebugConsole.ThrowError($"Reference joint index {limb.RefJointIndex} is out of array. This is probably due to a missing joint. If you just deleted a joint, don't do that without first removing the reference joint indices from the limbs. If this is not the case, please ensure that you have defined the index to the right joint."); } else { footPos.X = LimbJoints[limb.RefJointIndex].WorldAnchorA.X; } } footPos.X += limb.StepOffset.X * Dir; footPos.Y += limb.StepOffset.Y; if (limb.type == LimbType.LeftFoot) { limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( transformedStepSize.X + movement.X * 0.1f, (transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f); limb.MoveToPos(limb.DebugTargetPos, FootMoveForce); } else if (limb.type == LimbType.RightFoot) { limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( -transformedStepSize.X + movement.X * 0.1f, (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f); limb.MoveToPos(limb.DebugTargetPos, FootMoveForce); } if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID)) { SmoothRotateWithoutWrapping(limb, movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.limbParams.ID] * Dir, MainLimb, FootTorque); } break; case LimbType.LeftLeg: case LimbType.RightLeg: if (Math.Abs(CurrentGroundedParams.LegTorque) > 0.001f) { limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir); } break; } } }
protected void Apply(float deltaTime, Entity entity, List <ISerializableEntity> targets) { Hull hull = null; if (entity is Character) { hull = ((Character)entity).AnimController.CurrentHull; } else if (entity is Item) { hull = ((Item)entity).CurrentHull; } foreach (ISerializableEntity serializableEntity in targets) { if (!(serializableEntity is Item item)) { continue; } Character targetCharacter = targets.FirstOrDefault(t => t is Character character && !character.Removed) as Character; if (targetCharacter == null) { foreach (var target in targets) { if (target is Limb limb && limb.character != null && !limb.character.Removed) { targetCharacter = ((Limb)target).character; } } } for (int i = 0; i < useItemCount; i++) { if (item.Removed) { continue; } item.Use(deltaTime, targetCharacter, targets.FirstOrDefault(t => t is Limb) as Limb); } } if (removeItem) { foreach (Item item in targets.Where(t => t is Item).Cast <Item>()) { Entity.Spawner?.AddToRemoveQueue(item); } } if (duration > 0.0f) { DurationListElement element = new DurationListElement { Parent = this, Timer = duration, Entity = entity, Targets = targets }; DurationList.Add(element); } else { foreach (ISerializableEntity target in targets) { if (target is Entity targetEntity) { if (targetEntity.Removed) { continue; } } for (int i = 0; i < propertyNames.Length; i++) { if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(propertyNames[i], out SerializableProperty property)) { continue; } ApplyToProperty(target, property, propertyEffects[i], deltaTime); } } } if (explosion != null && entity != null) { explosion.Explode(entity.WorldPosition, damageSource: entity, attacker: user); } foreach (ISerializableEntity target in targets) { foreach (Affliction affliction in Afflictions) { Affliction multipliedAffliction = affliction; if (!disableDeltaTime) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); } if (target is Character character) { character.LastDamageSource = entity; foreach (Limb limb in character.AnimController.Limbs) { limb.character.DamageLimb(entity.WorldPosition, limb, new List <Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); //only apply non-limb-specific afflictions to the first limb if (!affliction.Prefab.LimbSpecific) { break; } } } else if (target is Limb limb) { limb.character.DamageLimb(entity.WorldPosition, limb, new List <Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); } } foreach (Pair <string, float> reduceAffliction in ReduceAffliction) { float reduceAmount = disableDeltaTime ? reduceAffliction.Second : reduceAffliction.Second * deltaTime; Limb targetLimb = null; Character targetCharacter = null; if (target is Character character) { targetCharacter = character; } else if (target is Limb limb) { targetLimb = limb; targetCharacter = limb.character; } if (targetCharacter != null) { float prevVitality = targetCharacter.Vitality; targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount); #if SERVER GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, prevVitality - targetCharacter.Vitality); #endif } } } if (FireSize > 0.0f && entity != null) { var fire = new FireSource(entity.WorldPosition, hull); fire.Size = new Vector2(FireSize, fire.Size.Y); } bool isNotClient = GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient; if (isNotClient && entity != null && Entity.Spawner != null) //clients are not allowed to spawn items { foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) { switch (itemSpawnInfo.SpawnPosition) { case ItemSpawnInfo.SpawnPositionType.This: Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, entity.WorldPosition); break; case ItemSpawnInfo.SpawnPositionType.ThisInventory: { if (entity is Character character) { if (character.Inventory != null && character.Inventory.Items.Any(it => it == null)) { Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, character.Inventory); } } else if (entity is Item item) { var inventory = item?.GetComponent <ItemContainer>()?.Inventory; if (inventory != null && inventory.Items.Any(it => it == null)) { Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, inventory); } } } break; case ItemSpawnInfo.SpawnPositionType.ContainedInventory: { Inventory thisInventory = null; if (entity is Character character) { thisInventory = character.Inventory; } else if (entity is Item item) { thisInventory = item?.GetComponent <ItemContainer>()?.Inventory; } if (thisInventory != null) { foreach (Item item in thisInventory.Items) { if (item == null) { continue; } Inventory containedInventory = item.GetComponent <ItemContainer>()?.Inventory; if (containedInventory == null || !containedInventory.Items.Any(i => i == null)) { continue; } Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, containedInventory); break; } } } break; } } } ApplyProjSpecific(deltaTime, entity, targets, hull); }
public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { switch (type) { case ServerNetObject.ENTITY_POSITION: bool facingRight = AnimController.Dir > 0.0f; lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now; AnimController.Frozen = false; Enabled = true; UInt16 networkUpdateID = 0; if (msg.ReadBoolean()) { networkUpdateID = msg.ReadUInt16(); } else { bool aimInput = msg.ReadBoolean(); keys[(int)InputType.Aim].Held = aimInput; keys[(int)InputType.Aim].SetState(false, aimInput); bool shootInput = msg.ReadBoolean(); keys[(int)InputType.Shoot].Held = shootInput; keys[(int)InputType.Shoot].SetState(false, shootInput); bool useInput = msg.ReadBoolean(); keys[(int)InputType.Use].Held = useInput; keys[(int)InputType.Use].SetState(false, useInput); if (AnimController is HumanoidAnimController) { bool crouching = msg.ReadBoolean(); keys[(int)InputType.Crouch].Held = crouching; keys[(int)InputType.Crouch].SetState(false, crouching); } bool attackInput = msg.ReadBoolean(); keys[(int)InputType.Attack].Held = attackInput; keys[(int)InputType.Attack].SetState(false, attackInput); double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI; cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f; TransformCursorPos(); bool ragdollInput = msg.ReadBoolean(); keys[(int)InputType.Ragdoll].Held = ragdollInput; keys[(int)InputType.Ragdoll].SetState(false, ragdollInput); facingRight = msg.ReadBoolean(); } bool entitySelected = msg.ReadBoolean(); Character selectedCharacter = null; Item selectedItem = null; AnimController.Animation animation = AnimController.Animation.None; if (entitySelected) { ushort characterID = msg.ReadUInt16(); ushort itemID = msg.ReadUInt16(); selectedCharacter = FindEntityByID(characterID) as Character; selectedItem = FindEntityByID(itemID) as Item; if (characterID != NullEntityID) { bool doingCpr = msg.ReadBoolean(); if (doingCpr && SelectedCharacter != null) { animation = AnimController.Animation.CPR; } } } Vector2 pos = new Vector2( msg.ReadSingle(), msg.ReadSingle()); float MaxVel = NetConfig.MaxPhysicsBodyVelocity; Vector2 linearVelocity = new Vector2( msg.ReadRangedSingle(-MaxVel, MaxVel, 12), msg.ReadRangedSingle(-MaxVel, MaxVel, 12)); linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12); bool fixedRotation = msg.ReadBoolean(); float?rotation = null; float?angularVelocity = null; if (!fixedRotation) { rotation = msg.ReadSingle(); float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8); angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8); } bool readStatus = msg.ReadBoolean(); if (readStatus) { ReadStatus(msg); (AIController as EnemyAIController)?.PetBehavior?.ClientRead(msg); } msg.ReadPadBits(); int index = 0; if (GameMain.Client.Character == this && CanMove) { var posInfo = new CharacterStateInfo( pos, rotation, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedCharacter, selectedItem, animation); while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID)) { index++; } memState.Insert(index, posInfo); } else { var posInfo = new CharacterStateInfo( pos, rotation, linearVelocity, angularVelocity, sendingTime, facingRight ? Direction.Right : Direction.Left, selectedCharacter, selectedItem, animation); while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp) { index++; } memState.Insert(index, posInfo); } break; case ServerNetObject.ENTITY_EVENT: int eventType = msg.ReadRangedInteger(0, 5); switch (eventType) { case 0: //NetEntityEvent.Type.InventoryState if (Inventory == null) { string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); //read anyway to prevent messing up reading the rest of the message UInt16 lastEventID = msg.ReadUInt16(); byte itemCount = msg.ReadByte(); for (int i = 0; i < itemCount; i++) { msg.ReadUInt16(); } } else { Inventory.ClientRead(type, msg, sendingTime); } break; case 1: //NetEntityEvent.Type.Control byte ownerID = msg.ReadByte(); ResetNetState(); if (ownerID == GameMain.Client.ID) { if (controlled != null) { LastNetworkUpdateID = controlled.LastNetworkUpdateID; } if (!IsDead) { Controlled = this; } IsRemotePlayer = false; GameMain.Client.HasSpawned = true; GameMain.Client.Character = this; GameMain.LightManager.LosEnabled = true; } else { if (controlled == this) { Controlled = null; IsRemotePlayer = ownerID > 0; } } break; case 2: //NetEntityEvent.Type.Status ReadStatus(msg); break; case 3: //NetEntityEvent.Type.UpdateSkills int skillCount = msg.ReadByte(); for (int i = 0; i < skillCount; i++) { string skillIdentifier = msg.ReadString(); float skillLevel = msg.ReadSingle(); info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f); } break; case 4: //NetEntityEvent.Type.ExecuteAttack int attackLimbIndex = msg.ReadByte(); UInt16 targetEntityID = msg.ReadUInt16(); int targetLimbIndex = msg.ReadByte(); //255 = entity already removed, no need to do anything if (attackLimbIndex == 255 || Removed) { break; } if (attackLimbIndex >= AnimController.Limbs.Length) { DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"); break; } Limb attackLimb = AnimController.Limbs[attackLimbIndex]; Limb targetLimb = null; if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity)) { DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target entity not found (ID {targetEntityID})"); break; } if (targetEntity is Character targetCharacter) { if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length) { DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})"); break; } targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; } if (attackLimb?.attack != null) { attackLimb.ExecuteAttack(targetEntity, targetLimb, out _); } break; case 5: //NetEntityEvent.Type.AssignCampaignInteraction byte campaignInteractionType = msg.ReadByte(); (GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType); break; } msg.ReadPadBits(); break; } }
public override void DragCharacter(Character target, float deltaTime) { if (target == null) { return; } Limb mouthLimb = Array.Find(Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) { mouthLimb = GetLimb(LimbType.Head); } if (mouthLimb == null) { DebugConsole.ThrowError("Character \"" + character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); return; } Character targetCharacter = target; float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f; eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = GetMouthPosition().Value; Vector2 attackSimPosition = character.Submarine == null?ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float limbDist = limbDiff.Length(); if (limbDist < 1.0f) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) if (CanDrag(target)) { targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, 20.0f); targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); } //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); character.ApplyStatusEffects(ActionType.OnEating, deltaTime); if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { //apply damage to the target character to get some blood particles flying targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, 0.0f, 20.0f, 0.0f, false); //keep severing joints until there is only one limb left LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered && l.CanBeSevered && l.LimbA != null && !l.LimbA.IsSevered && l.LimbB != null && !l.LimbB.IsSevered); if (nonSeveredJoints.Length == 0) { //only one limb left, the character is now full eaten Entity.Spawner.AddToRemoveQueue(targetCharacter); character.SelectedCharacter = null; } else //sever a random joint { targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); } } } else { character.SelectedCharacter = null; } }
private void UpdateAttack(float deltaTime) { if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) { state = AiState.None; return; } selectedTargetMemory.Priority -= deltaTime; Vector2 attackSimPosition = Character.Submarine == null?ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition; if (wallAttackPos != Vector2.Zero && targetEntity != null) { attackSimPosition = wallAttackPos; if (selectedAiTarget.Entity != null && Character.Submarine == null && selectedAiTarget.Entity.Submarine != null) { attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position); } } if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater) { Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left; } if (coolDownTimer > 0.0f) { UpdateCoolDown(attackSimPosition, deltaTime); return; } if (raycastTimer > 0.0) { raycastTimer -= deltaTime; } else { GetTargetEntity(); raycastTimer = RaycastInterval; } //steeringManager.SteeringAvoid(deltaTime, 1.0f); Limb attackLimb = attackingLimb; //check if any of the limbs is close enough to attack the target if (attackingLimb == null) { foreach (Limb limb in Character.AnimController.Limbs) { if (limb.attack == null) { continue; } attackLimb = limb; if (ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.SimPosition, attackSimPosition)) > limb.attack.Range) { continue; } attackingLimb = limb; break; } if (Character.IsRemotePlayer) { if (!Character.IsKeyDown(InputType.Attack)) { return; } } } if (attackLimb != null) { steeringManager.SteeringSeek(attackSimPosition - (attackLimb.SimPosition - SimPosition)); if (steeringManager is IndoorsSteeringManager) { var indoorsSteering = (IndoorsSteeringManager)steeringManager; if (indoorsSteering.CurrentPath != null && (indoorsSteering.CurrentPath.Finished || indoorsSteering.CurrentPath.Unreachable)) { steeringManager.SteeringManual(deltaTime, attackSimPosition - attackLimb.SimPosition); } } if (attackingLimb != null) { UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition); } } }
private void HandleLimbCollision(Impact collision, Limb limb) { if (limb?.body?.FarseerBody == null || limb.character == null) { return; } if (limb.Mass > MinImpactLimbMass) { Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? Vector2.UnitY : Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(limb.Mass / 100.0f, 1); ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge?.Contact != null) { if (contactEdge.Contact.Enabled && contactEdge.Contact.IsTouching && contactEdge.Other?.UserData is VoronoiCell) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 <Vector2> temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, collision.ImpactPos, applyDamage: false); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.001f) { Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; if (!MathUtils.IsValid(velChange)) { GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.HandleLimbCollision:" + submarine.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + ", avgContactNormal: " + avgContactNormal + ", contactDot: " + contactDot + ", velChange: " + velChange + ")"); return; } Body.LinearVelocity -= velChange; float damageAmount = contactDot * Body.Mass / limb.character.Mass; limb.character.LastDamageSource = submarine; limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } }
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { string errorMsg = ""; if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) { if (extraData == null) { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was null."; } else if (extraData.Length == 0) { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was empty."; } else { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event type not set."; } msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } int initialWritePos = msg.LengthBits; NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; msg.WriteRangedInteger((int)eventType, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); switch (eventType) { case NetEntityEvent.Type.ComponentState: if (extraData.Length < 2 || !(extraData[1] is int)) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; break; } int componentIndex = (int)extraData[1]; if (componentIndex < 0 || componentIndex >= components.Count) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ")."; break; } else if (!(components[componentIndex] is IServerSerializable)) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable."; break; } msg.WriteRangedInteger(componentIndex, 0, components.Count - 1); (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); break; case NetEntityEvent.Type.InventoryState: if (extraData.Length < 2 || !(extraData[1] is int)) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component index not given."; break; } int containerIndex = (int)extraData[1]; if (containerIndex < 0 || containerIndex >= components.Count) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - container index out of range (" + containerIndex + ")."; break; } else if (!(components[containerIndex] is ItemContainer)) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component \"" + components[containerIndex] + "\" is not server serializable."; break; } msg.WriteRangedInteger(containerIndex, 0, components.Count - 1); msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); (components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Status: msg.Write(condition); break; case NetEntityEvent.Type.Treatment: { ItemComponent targetComponent = (ItemComponent)extraData[1]; ActionType actionType = (ActionType)extraData[2]; ushort targetID = (ushort)extraData[3]; Limb targetLimb = (Limb)extraData[4]; Character targetCharacter = FindEntityByID(targetID) as Character; byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.Write((byte)components.IndexOf(targetComponent)); msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1); msg.Write(targetID); msg.Write(targetLimbIndex); } break; case NetEntityEvent.Type.ApplyStatusEffect: { ActionType actionType = (ActionType)extraData[1]; ItemComponent targetComponent = extraData.Length > 2 ? (ItemComponent)extraData[2] : null; ushort characterID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0; Limb targetLimb = extraData.Length > 4 ? (Limb)extraData[4] : null; ushort useTargetID = extraData.Length > 5 ? (ushort)extraData[5] : (ushort)0; Vector2? worldPosition = null; if (extraData.Length > 6) { worldPosition = (Vector2)extraData[6]; } Character targetCharacter = FindEntityByID(characterID) as Character; byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1); msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent))); msg.Write(characterID); msg.Write(targetLimbIndex); msg.Write(useTargetID); msg.Write(worldPosition.HasValue); if (worldPosition.HasValue) { msg.Write(worldPosition.Value.X); msg.Write(worldPosition.Value.Y); } } break; case NetEntityEvent.Type.ChangeProperty: try { WritePropertyChange(msg, extraData, inGameEditableOnly: !GameMain.NetworkMember.IsServer); } catch (Exception e) { errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")"; } break; case NetEntityEvent.Type.Upgrade: if (extraData.Length > 0 && extraData[1] is Upgrade upgrade) { var upgradeTargets = upgrade.TargetComponents; msg.Write(upgrade.Identifier); msg.Write((byte)upgrade.Level); msg.Write((byte)upgradeTargets.Count); foreach (var(_, value) in upgrade.TargetComponents) { msg.Write((byte)value.Length); foreach (var propertyReference in value) { object originalValue = propertyReference.OriginalValue; msg.Write((float)(originalValue ?? -1)); } } } else { errorMsg = extraData.Length > 0 ? $"Failed to write a network event for the item \"{Name}\" - \"{extraData[1].GetType()}\" is not a valid upgrade." : $"Failed to write a network event for the item \"{Name}\". No upgrade specified."; } break; default: errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; break; } if (!string.IsNullOrEmpty(errorMsg)) { //something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event msg.BitPosition = initialWritePos; msg.LengthBits = initialWritePos; msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } }
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) { NetEntityEvent.Type eventType = (NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); c.KickAFKTimer = 0.0f; switch (eventType) { case NetEntityEvent.Type.ComponentState: int componentIndex = msg.ReadRangedInteger(0, components.Count - 1); (components[componentIndex] as IClientSerializable).ServerRead(type, msg, c); break; case NetEntityEvent.Type.InventoryState: int containerIndex = msg.ReadRangedInteger(0, components.Count - 1); (components[containerIndex] as ItemContainer).Inventory.ServerRead(type, msg, c); break; case NetEntityEvent.Type.Treatment: if (c.Character == null || !c.Character.CanInteractWith(this)) { return; } UInt16 characterID = msg.ReadUInt16(); byte limbIndex = msg.ReadByte(); Character targetCharacter = FindEntityByID(characterID) as Character; if (targetCharacter == null) { break; } if (targetCharacter != c.Character && c.Character.SelectedCharacter != targetCharacter) { break; } Limb targetLimb = limbIndex < targetCharacter.AnimController.Limbs.Length ? targetCharacter.AnimController.Limbs[limbIndex] : null; if (ContainedItems == null || ContainedItems.All(i => i == null)) { GameServer.Log(GameServer.CharacterLogName(c.Character) + " used item " + Name, ServerLog.MessageType.ItemInteraction); } else { GameServer.Log( GameServer.CharacterLogName(c.Character) + " used item " + Name + " (contained items: " + string.Join(", ", ContainedItems.Select(i => i.Name)) + ")", ServerLog.MessageType.ItemInteraction); } ApplyTreatment(c.Character, targetCharacter, targetLimb); break; case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg, inGameEditableOnly: GameMain.NetworkMember.IsServer, sender: c); break; case NetEntityEvent.Type.Combine: UInt16 combineTargetID = msg.ReadUInt16(); Item combineTarget = FindEntityByID(combineTargetID) as Item; if (combineTarget == null || !c.Character.CanInteractWith(this) || !c.Character.CanInteractWith(combineTarget)) { return; } Combine(combineTarget, c.Character); break; } }
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { string errorMsg = ""; if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) { if (extraData == null) { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was null."; } else if (extraData.Length == 0) { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was empty."; } else { errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event type not set."; } msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return; } int initialWritePos = msg.LengthBits; NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType); switch (eventType) { case NetEntityEvent.Type.ComponentState: if (extraData.Length < 2 || !(extraData[1] is int)) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given."; break; } int componentIndex = (int)extraData[1]; if (componentIndex < 0 || componentIndex >= components.Count) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ")."; break; } else if (!(components[componentIndex] is IServerSerializable)) { errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable."; break; } msg.WriteRangedInteger(0, components.Count - 1, componentIndex); (components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData); break; case NetEntityEvent.Type.InventoryState: if (extraData.Length < 2 || !(extraData[1] is int)) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component index not given."; break; } int containerIndex = (int)extraData[1]; if (containerIndex < 0 || containerIndex >= components.Count) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - container index out of range (" + containerIndex + ")."; break; } else if (!(components[containerIndex] is ItemContainer)) { errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component \"" + components[containerIndex] + "\" is not server serializable."; break; } msg.WriteRangedInteger(0, components.Count - 1, containerIndex); (components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Status: msg.Write(condition); break; case NetEntityEvent.Type.Treatment: { ItemComponent targetComponent = (ItemComponent)extraData[1]; ActionType actionType = (ActionType)extraData[2]; ushort targetID = (ushort)extraData[3]; Limb targetLimb = (Limb)extraData[4]; Character targetCharacter = FindEntityByID(targetID) as Character; byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.Write((byte)components.IndexOf(targetComponent)); msg.WriteRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType); msg.Write(targetID); msg.Write(targetLimbIndex); } break; case NetEntityEvent.Type.ApplyStatusEffect: { ActionType actionType = (ActionType)extraData[1]; ItemComponent targetComponent = extraData.Length > 2 ? (ItemComponent)extraData[2] : null; ushort targetID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0; Limb targetLimb = extraData.Length > 4 ? (Limb)extraData[4] : null; Character targetCharacter = FindEntityByID(targetID) as Character; byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255; msg.WriteRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType); msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent))); msg.Write(targetID); msg.Write(targetLimbIndex); } break; case NetEntityEvent.Type.ChangeProperty: try { WritePropertyChange(msg, extraData, false); } catch (Exception e) { errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")"; } break; default: errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; break; } if (!string.IsNullOrEmpty(errorMsg)) { //something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event msg.ReadBits(msg.Data, 0, initialWritePos); msg.LengthBits = initialWritePos; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid); DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } }
private void HandleLimbCollision(Contact contact, Limb limb) { if (limb.Mass > 100.0f) { Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); ApplyImpact(impact, -normal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, contact); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge.Next != null) { if (contactEdge.Contact.Enabled && contactEdge.Other.UserData is VoronoiCell && contactEdge.Contact.IsTouching) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage //TODO: apply impact damage and/or gib the character that got crushed between the sub and the level? Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { Vector2 contactNormal; FixedArray2 <Vector2> temp; levelContact.GetWorldManifold(out contactNormal, out temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.Body.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.Body.UserData) : ((VoronoiCell)levelContact.FixtureA.Body.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, levelContact); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.0f) { Body.LinearVelocity -= Vector2.Normalize(Body.LinearVelocity) * contactDot; float damageAmount = contactDot * Body.Mass / limb.character.Mass; Vector2 n; FixedArray2 <Vector2> contactPos; contact.GetWorldManifold(out n, out contactPos); limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb, DamageType.Blunt, damageAmount, 0.0f, 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } }
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { if (!Visible) { return; } Color color = (IsSelected && editing) ? Color.Red : GetSpriteColor(); if (isHighlighted) { color = Color.Orange; } Sprite activeSprite = prefab.sprite; BrokenItemSprite fadeInBrokenSprite = null; float fadeInBrokenSpriteAlpha = 0.0f; if (condition < 100.0f) { for (int i = 0; i < prefab.BrokenSprites.Count; i++) { if (condition <= prefab.BrokenSprites[i].MaxCondition) { activeSprite = prefab.BrokenSprites[i].Sprite; break; } if (prefab.BrokenSprites[i].FadeIn) { float min = i > 0 ? prefab.BrokenSprites[i].MaxCondition : 0.0f; float max = i < prefab.BrokenSprites.Count - 1 ? prefab.BrokenSprites[i + 1].MaxCondition : 100.0f; fadeInBrokenSpriteAlpha = 1.0f - ((condition - min) / (max - min)); if (fadeInBrokenSpriteAlpha > 0.0f && fadeInBrokenSpriteAlpha < 1.0f) { fadeInBrokenSprite = prefab.BrokenSprites[i]; } } } } Sprite selectedSprite = prefab.GetActiveSprite(condition); if (selectedSprite != null) { SpriteEffects oldEffects = selectedSprite.effects; selectedSprite.effects ^= SpriteEffects; float depth = GetDrawDepth(); if (body == null) { if (prefab.ResizeHorizontal || prefab.ResizeVertical || SpriteEffects.HasFlag(SpriteEffects.FlipHorizontally) || SpriteEffects.HasFlag(SpriteEffects.FlipVertically)) { selectedSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), color); fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), color * fadeInBrokenSpriteAlpha, Point.Zero, selectedSprite.Depth - 0.000001f); } else { selectedSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), color, 0.0f, 1.0f, SpriteEffects.None, depth); fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), color * fadeInBrokenSpriteAlpha, 0.0f, 1.0f, SpriteEffects.None, depth - 0.000001f); } } else if (body.Enabled) { var holdable = GetComponent <Holdable>(); if (holdable != null && holdable.Picker?.AnimController != null) { if (holdable.Picker.SelectedItems[0] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand); depth = holdLimb.sprite.Depth + 0.000001f; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) { if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) { depth = Math.Min(wearableSprite.Sprite.Depth, depth); } } } else if (holdable.Picker.SelectedItems[1] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand); depth = holdLimb.sprite.Depth - 0.000001f; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) { if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) { depth = Math.Max(wearableSprite.Sprite.Depth, depth); } } } } body.Draw(spriteBatch, selectedSprite, color, depth); if (fadeInBrokenSprite != null) { body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, depth - 0.000001f); } } selectedSprite.effects = oldEffects; } List <IDrawableComponent> staticDrawableComponents = new List <IDrawableComponent>(drawableComponents); //static list to compensate for drawable toggling for (int i = 0; i < staticDrawableComponents.Count; i++) { staticDrawableComponents[i].Draw(spriteBatch, editing); } if (GameMain.DebugDraw && aiTarget != null) { aiTarget.Draw(spriteBatch); } if (!editing || (body != null && !body.Enabled)) { return; } if (IsSelected || isHighlighted) { GUI.DrawRectangle(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), Color.Green, false, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); foreach (Rectangle t in prefab.Triggers) { Rectangle transformedTrigger = TransformTrigger(t); Vector2 rectWorldPos = new Vector2(transformedTrigger.X, transformedTrigger.Y); if (Submarine != null) { rectWorldPos += Submarine.Position; } rectWorldPos.Y = -rectWorldPos.Y; GUI.DrawRectangle(spriteBatch, rectWorldPos, new Vector2(transformedTrigger.Width, transformedTrigger.Height), Color.Green, false, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); } } if (!ShowLinks) { return; } foreach (MapEntity e in linkedTo) { GUI.DrawLine(spriteBatch, new Vector2(WorldPosition.X, -WorldPosition.Y), new Vector2(e.WorldPosition.X, -e.WorldPosition.Y), Color.Red * 0.3f); } }
public override void UpdateAnim(float deltaTime) { if (Frozen) { return; } if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f) { Collider.FarseerBody.FixedRotation = false; if (character.IsRemotePlayer) { if (!SimplePhysicsEnabled) { MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointEnabled = true; } } else { Vector2 diff = (MainLimb.SimPosition - Collider.SimPosition); if (diff.LengthSquared() > 10.0f * 10.0f) { Collider.SetTransform(MainLimb.SimPosition, MainLimb.Rotation); } else { Collider.LinearVelocity = diff * 60.0f; Collider.SmoothRotate(MainLimb.Rotation); } } if (character.IsDead && deathAnimTimer < deathAnimDuration) { deathAnimTimer += deltaTime; UpdateDying(deltaTime); } return; } //re-enable collider if (!Collider.Enabled) { var lowestLimb = FindLowestLimb(); Collider.SetTransform(new Vector2( Collider.SimPosition.X, Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)), 0.0f); Collider.Enabled = true; } ResetPullJoints(); if (strongestImpact > 0.0f) { character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f); strongestImpact = 0.0f; } if (inWater) { Collider.FarseerBody.FixedRotation = false; UpdateSineAnim(deltaTime); } else if (currentHull != null && CanEnterSubmarine) { if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f) { //rotate collider back upright Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 60.0f; Collider.FarseerBody.FixedRotation = false; } else { Collider.FarseerBody.FixedRotation = true; } UpdateWalkAnim(deltaTime); } //don't flip or drag when simply physics is enabled if (SimplePhysicsEnabled) { return; } if (!character.IsRemotePlayer) { if (mirror || !inWater) { if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.5f) { TargetDir = Direction.Right; } else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.5f) { TargetDir = Direction.Left; } } else { Limb head = GetLimb(LimbType.Head); if (head == null) { head = GetLimb(LimbType.Torso); } float rotation = MathUtils.WrapAngleTwoPi(head.Rotation); rotation = MathHelper.ToDegrees(rotation); if (rotation < 0.0f) { rotation += 360; } if (rotation > 20 && rotation < 160) { TargetDir = Direction.Left; } else if (rotation > 200 && rotation < 340) { TargetDir = Direction.Right; } } } if (character.SelectedCharacter != null) { DragCharacter(character.SelectedCharacter); } if (!flip) { return; } flipTimer += deltaTime; if (TargetDir != Direction.None && TargetDir != dir) { if (flipTimer > 1.0f || character.IsRemotePlayer) { Flip(); if (mirror || !inWater) { Mirror(); } flipTimer = 0.0f; } } }
private void UpdateEating(float deltaTime) { if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) { state = AIState.None; return; } Limb mouthLimb = Array.Find(Character.AnimController.Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) { mouthLimb = Character.AnimController.GetLimb(LimbType.Head); } if (mouthLimb == null) { DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); state = AIState.None; return; } Character targetCharacter = selectedAiTarget.Entity as Character; float eatSpeed = Character.Mass / targetCharacter.Mass * 0.1f; eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = mouthLimb.SimPosition; if (mouthLimb.MouthPos.HasValue) { float cos = (float)Math.Cos(mouthLimb.Rotation); float sin = (float)Math.Sin(mouthLimb.Rotation); mouthPos += new Vector2( mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin, mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos); } Vector2 attackSimPosition = Character.Submarine == null?ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float limbDist = limbDiff.Length(); if (limbDist < 1.0f) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation); targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); steeringManager.SteeringManual(deltaTime, limbDiff * pullStrength); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); //GameMain.NilMod.CreatureHealthGainEatingPercent; if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { //Kill living players that control the character if being actively consumed. //Before this point is reached a fish could be prevented with stun from severing limbs and thus still save a player. if (!targetCharacter.IsDead && targetCharacter.IsRemotePlayer) { targetCharacter.Kill(CauseOfDeath.Damage, true); } //apply damage to the target character to get some blood particles flying targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), Rand.Range(15.0f, 40.0f), false); //Lets make this extra bloody, perhaps a client will sync it. targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), Rand.Range(15.0f, 40.0f), false); targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), Rand.Range(15.0f, 40.0f), false); targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), Rand.Range(15.0f, 40.0f), false); //keep severing joints until there is only one limb left LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered && l.CanBeSevered); if (nonSeveredJoints.Length == 0) { //only one limb left, the character is now full eaten if (GameMain.Server != null) { GameMain.Server.ServerLog.WriteLine(Character.Name + " has finished eating " + targetCharacter.Name, Networking.ServerLog.MessageType.Spawns); } Entity.Spawner.AddToRemoveQueue(targetCharacter); selectedAiTarget = null; state = AIState.None; } else //sever a random joint { targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); if (GameMain.Server != null) { GameMain.Server.CreateEntityEvent(targetCharacter, new object[] { Barotrauma.Networking.NetEntityEvent.Type.Status }); } } } } else if (limbDist < 2.0f) { steeringManager.SteeringManual(deltaTime, limbDiff); Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); } else { steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 3); } }
/// <summary> /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// </summary> public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null) { attackResult = default(AttackResult); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos)); bool wasRunning = attack.IsRunning; attack.UpdateAttackTimer(deltaTime); bool wasHit = false; Body structureBody = null; if (damageTarget != null) { switch (attack.HitDetectionType) { case HitDetection.Distance: if (dist < attack.DamageRange) { if (ignoredBodies == null) { ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList(); ignoredBodies.Add(character.AnimController.Collider.FarseerBody); } structureBody = Submarine.PickBody( SimPosition, attackSimPos, ignoredBodies, Physics.CollisionWall); if (damageTarget is Item) { // If the attack is aimed to an item and hits an item, it's successful. // Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example. wasHit = true; } else if (damageTarget is Structure wall && structureBody != null && (structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine))) { // If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful wasHit = true; } else { // If there is nothing between, the hit is successful wasHit = structureBody == null; } } break;
private void UpdateEating(float deltaTime) { if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed) { state = AIState.None; return; } Limb mouthLimb = Array.Find(Character.AnimController.Limbs, l => l != null && l.MouthPos.HasValue); if (mouthLimb == null) { mouthLimb = Character.AnimController.GetLimb(LimbType.Head); } if (mouthLimb == null) { DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)"); state = AIState.None; return; } Character targetCharacter = selectedAiTarget.Entity as Character; float eatSpeed = Character.Mass / targetCharacter.Mass * 0.1f; eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = mouthLimb.SimPosition; if (mouthLimb.MouthPos.HasValue) { float cos = (float)Math.Cos(mouthLimb.Rotation); float sin = (float)Math.Sin(mouthLimb.Rotation); mouthPos += new Vector2( mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin, mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos); } Vector2 attackSimPosition = Character.Submarine == null?ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float limbDist = limbDiff.Length(); if (limbDist < 1.0f) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation); targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f)); //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); steeringManager.SteeringManual(deltaTime, limbDiff * pullStrength); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { //apply damage to the target character to get some blood particles flying targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), 10.0f, false); //keep severing joints until there is only one limb left LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered); if (nonSeveredJoints.Length == 0) { //only one limb left, the character is now full eaten Entity.Spawner.AddToRemoveQueue(targetCharacter); selectedAiTarget = null; state = AIState.None; } else //sever a random joint { targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]); } } } else if (limbDist < 2.0f) { steeringManager.SteeringManual(deltaTime, limbDiff); Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); } else { steeringManager.SteeringSeek(attackSimPosition + (mouthPos - SimPosition), 3); } }
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect) { if (wearable.InheritSourceRect) { if (wearable.SheetIndex.HasValue) { Point location = (ActiveSprite.SourceRect.Location + ActiveSprite.SourceRect.Size) * wearable.SheetIndex.Value; wearable.Sprite.SourceRect = new Rectangle(location, ActiveSprite.SourceRect.Size); } else { wearable.Sprite.SourceRect = ActiveSprite.SourceRect; } } Vector2 origin = wearable.Sprite.Origin; if (wearable.InheritOrigin) { origin = ActiveSprite.Origin; wearable.Sprite.Origin = origin; } else { origin = wearable.Sprite.Origin; // If the wearable inherits the origin, flipping is already handled. if (body.Dir == -1.0f) { origin.X = wearable.Sprite.SourceRect.Width - origin.X; } } float depth = wearable.Sprite.Depth; if (wearable.InheritLimbDepth) { depth = ActiveSprite.Depth - depthStep; if (wearable.DepthLimb != LimbType.None) { Limb depthLimb = character.AnimController.GetLimb(wearable.DepthLimb); if (depthLimb != null) { depth = depthLimb.ActiveSprite.Depth - depthStep; } } } var wearableItemComponent = wearable.WearableComponent; Color wearableColor = Color.White; if (wearableItemComponent != null) { // Draw outer cloths on top of inner cloths. if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes)) { depth -= depthStep; } wearableColor = wearableItemComponent.Item.GetSpriteColor(); } float textureScale = wearable.InheritTextureScale ? TextureScale : 1; wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)), origin, -body.DrawRotation, Scale * textureScale, spriteEffect, depth); }
public static List <Limb> AttachHuskAppendage(Character character, string afflictionIdentifier, XElement appendageDefinition = null, Ragdoll ragdoll = null) { var appendage = new List <Limb>(); if (!(AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier) is AfflictionPrefabHusk matchingAffliction)) { DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!"); return(appendage); } string nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction); string huskedSpeciesName = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction); string filePath = Character.GetConfigFilePath(huskedSpeciesName); if (!Character.TryGetConfigFile(filePath, out XDocument huskDoc)) { DebugConsole.ThrowError($"Error in '{filePath}': Failed to load the config file for the husk infected species with the species name '{huskedSpeciesName}'!"); return(appendage); } var mainElement = huskDoc.Root.IsOverride() ? huskDoc.Root.FirstElement() : huskDoc.Root; var element = appendageDefinition; if (element == null) { element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeString("affliction", string.Empty).Equals(afflictionIdentifier)); } if (element == null) { DebugConsole.ThrowError($"Error in '{filePath}': Failed to find a huskappendage that matches the affliction with an identifier '{afflictionIdentifier}'!"); return(appendage); } string pathToAppendage = element.GetAttributeString("path", string.Empty); XDocument doc = XMLExtensions.TryLoadXml(pathToAppendage); if (doc == null) { return(appendage); } if (ragdoll == null) { ragdoll = character.AnimController; } if (ragdoll.Dir < 1.0f) { ragdoll.Flip(); } var limbElements = doc.Root.Elements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e); foreach (var jointElement in doc.Root.Elements("joint")) { if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out XElement limbElement)) { var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams); Limb attachLimb = null; if (matchingAffliction.AttachLimbId > -1) { attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == matchingAffliction.AttachLimbId); } else if (matchingAffliction.AttachLimbName != null) { attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Name == matchingAffliction.AttachLimbName); } else if (matchingAffliction.AttachLimbType != LimbType.None) { attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.type == matchingAffliction.AttachLimbType); } if (attachLimb == null) { DebugConsole.Log("Attachment limb not defined in the affliction prefab or no matching limb could be found. Using the appendage definition as it is."); attachLimb = ragdoll.Limbs.FirstOrDefault(l => l.Params.ID == jointParams.Limb1); } if (attachLimb != null) { jointParams.Limb1 = attachLimb.Params.ID; var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams) { // Ensure that we have a valid id for the new limb ID = ragdoll.Limbs.Length }; jointParams.Limb2 = appendageLimbParams.ID; Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams); huskAppendage.body.Submarine = character.Submarine; huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation); ragdoll.AddLimb(huskAppendage); ragdoll.AddJoint(jointParams); appendage.Add(huskAppendage); } else { DebugConsole.ThrowError("Attachment limb not found!"); } } } return(appendage); }
public void Draw(SpriteBatch spriteBatch) { float brightness = 1.0f - (burnt / 100.0f) * 0.5f; Color color = new Color(brightness, brightness, brightness); if (isSevered) { if (severedFadeOutTimer > SeveredFadeOutTime) { return; } else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f) { color *= SeveredFadeOutTime - severedFadeOutTimer; } } body.Dir = Dir; bool hideLimb = wearingItems.Any(w => w != null && w.HideLimb); if (!hideLimb) { body.Draw(spriteBatch, sprite, color, null, scale); } else { body.UpdateDrawPosition(); } if (LightSource != null) { LightSource.Position = body.DrawPosition; LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; } foreach (WearableSprite wearable in wearingItems) { SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally; Vector2 origin = wearable.Sprite.Origin; if (body.Dir == -1.0f) { origin.X = wearable.Sprite.SourceRect.Width - origin.X; } float depth = wearable.Sprite.Depth; if (wearable.InheritLimbDepth) { depth = sprite.Depth - 0.000001f; if (wearable.DepthLimb != LimbType.None) { Limb depthLimb = character.AnimController.GetLimb(wearable.DepthLimb); if (depthLimb != null) { depth = depthLimb.sprite.Depth - 0.000001f; } } } wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), color, origin, -body.DrawRotation, scale, spriteEffect, depth); } if (damage > 0.0f && damagedSprite != null && !hideLimb) { SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally; float depth = sprite.Depth - 0.0000015f; damagedSprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), color * Math.Min(damage / 50.0f, 1.0f), sprite.Origin, -body.DrawRotation, 1.0f, spriteEffect, depth); } if (!GameMain.DebugDraw) { return; } if (pullJoint != null) { Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.Red, true); } }
public override void DragCharacter(Character target, float deltaTime) { if (target == null) { return; } Limb mouthLimb = GetLimb(LimbType.Head); if (mouthLimb == null) { return; } if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { //stop dragging if there's something between the pull limb and the target Vector2 sourceSimPos = SimplePhysicsEnabled ? character.SimPosition : mouthLimb.SimPosition; Vector2 targetSimPos = target.SimPosition; if (character.Submarine != null && character.SelectedCharacter.Submarine == null) { targetSimPos -= character.Submarine.SimPosition; } else if (character.Submarine == null && character.SelectedCharacter.Submarine != null) { sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition; } var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true); if (body != null) { character.DeselectCharacter(); return; } } float dmg = character.Params.EatingSpeed; float eatSpeed = dmg / ((float)Math.Sqrt(Math.Max(target.Mass, 1)) * 10); eatTimer += deltaTime * eatSpeed; Vector2 mouthPos = SimplePhysicsEnabled ? character.SimPosition : GetMouthPosition().Value; Vector2 attackSimPosition = character.Submarine == null?ConvertUnits.ToSimUnits(target.WorldPosition) : target.SimPosition; Vector2 limbDiff = attackSimPosition - mouthPos; float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1); if (limbDiff.LengthSquared() < extent * extent) { //pull the target character to the position of the mouth //(+ make the force fluctuate to waggle the character a bit) float dragForce = MathHelper.Clamp(eatSpeed * 10, 0, 40); if (dragForce > 0.1f) { target.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce)); target.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation, dragForce * 2); target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce)); } if (InWater) { //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } else { float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass; mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); mouthLimb.body.ApplyTorque(-force * 50); } var jaw = GetLimb(LimbType.Jaw); if (jaw != null) { jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25); } character.ApplyStatusEffects(ActionType.OnEating, deltaTime); float particleFrequency = MathHelper.Clamp(eatSpeed / 2, 0.02f, 0.5f); if (Rand.Value() < particleFrequency / 6) { target.AnimController.MainLimb.AddDamage(target.SimPosition, dmg, 0, 0, false); } if (Rand.Value() < particleFrequency) { target.AnimController.MainLimb.AddDamage(target.SimPosition, 0, dmg, 0, false); } if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered; //keep severing joints until there is only one limb left var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered); if (nonSeveredJoints.None()) { //only one limb left, the character is now full eaten Entity.Spawner?.AddToRemoveQueue(target); character.SelectedCharacter = null; } else //sever a random joint { target.AnimController.SeverLimbJoint(nonSeveredJoints.GetRandom()); } } } else { character.SelectedCharacter = null; } }
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime) { base.Update(characterHealth, targetLimb, deltaTime); characterHealth.BloodlossAmount += Strength * (1.0f / 60.0f) * deltaTime; }
void UpdateSineAnim(float deltaTime) { if (CurrentSwimParams == null) { return; } movement = TargetMovement; if (movement.LengthSquared() > 0.00001f) { float t = 0.5f; if (CurrentSwimParams.RotateTowardsMovement && VectorExtensions.Angle(VectorExtensions.Forward(Collider.Rotation + MathHelper.PiOver2), movement) > MathHelper.PiOver2) { // Reduce the linear movement speed when not facing the movement direction t /= 5; } Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, t); } //limbs are disabled when simple physics is enabled, no need to move them if (SimplePhysicsEnabled) { return; } var mainLimb = MainLimb; mainLimb.PullJointEnabled = true; //mainLimb.PullJointWorldAnchorB = Collider.SimPosition; if (movement.LengthSquared() < 0.00001f) { WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5); mainLimb.PullJointWorldAnchorB = Collider.SimPosition; return; } Vector2 transformedMovement = reverse ? -movement : movement; float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2; float mainLimbAngle = 0; if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) { mainLimbAngle = TorsoAngle.Value; } else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) { mainLimbAngle = HeadAngle.Value; } mainLimbAngle *= Dir; while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { movementAngle += MathHelper.TwoPi; } while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { movementAngle -= MathHelper.TwoPi; } if (CurrentSwimParams.RotateTowardsMovement) { Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); if (TorsoAngle.HasValue) { Limb torso = GetLimb(LimbType.Torso); if (torso != null) { SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque); } } if (HeadAngle.HasValue) { Limb head = GetLimb(LimbType.Head); if (head != null) { SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque); } } if (TailAngle.HasValue) { Limb tail = GetLimb(LimbType.Tail); if (tail != null) { float?mainLimbTargetAngle = null; if (mainLimb.type == LimbType.Torso) { mainLimbTargetAngle = TorsoAngle; } else if (mainLimb.type == LimbType.Head) { mainLimbTargetAngle = HeadAngle; } float torque = TailTorque; float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier; if (mainLimbTargetAngle.HasValue && maxMultiplier > 1) { float diff = Math.Abs(mainLimb.Rotation - tail.Rotation); float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value); torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset)); } SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque); } } } else { movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; if (reverse) { movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); } if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) { Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); } else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) { Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); } if (TorsoAngle.HasValue) { Limb torso = GetLimb(LimbType.Torso); torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque); } if (HeadAngle.HasValue) { Limb head = GetLimb(LimbType.Head); head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque); } if (TailAngle.HasValue) { Limb tail = GetLimb(LimbType.Tail); tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque); } } var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier); if (waveLength > 0 && waveAmplitude > 0) { WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); } foreach (var limb in Limbs) { if (limb.IsSevered) { continue; } if (Math.Abs(limb.Params.ConstantTorque) > 0) { limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); } switch (limb.type) { case LimbType.LeftFoot: case LimbType.RightFoot: if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID)) { SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque); } break; case LimbType.Tail: if (waveLength > 0 && waveAmplitude > 0) { float waveRotation = (float)Math.Sin(WalkPos); limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude); } break; } } for (int i = 0; i < Limbs.Length; i++) { var limb = Limbs[i]; if (limb.IsSevered) { continue; } if (limb.SteerForce <= 0.0f) { continue; } if (!Collider.PhysEnabled) { continue; } Vector2 pullPos = limb.PullJointWorldAnchorA; limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos); } Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition; if (CurrentSwimParams.UseSineMovement) { mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep( mainLimb.PullJointWorldAnchorB, Collider.SimPosition, mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos))); } else { //mainLimb.PullJointWorldAnchorB = Collider.SimPosition; mainLimb.PullJointWorldAnchorB = Vector2.Lerp( mainLimb.PullJointWorldAnchorB, Collider.SimPosition, mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f); } floorY = Limbs[0].SimPosition.Y; }
public override void UpdateAnim(float deltaTime) { if (Frozen) { return; } if (MainLimb == null) { return; } if (!character.AllowInput) { levitatingCollider = false; Collider.FarseerBody.FixedRotation = false; if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { Collider.LinearVelocity = MainLimb.LinearVelocity; Collider.FarseerBody.FixedRotation = false; Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation); } if (character.IsDead && deathAnimTimer < deathAnimDuration) { deathAnimTimer += deltaTime; UpdateDying(deltaTime); } return; } else { deathAnimTimer = 0.0f; } //re-enable collider if (!Collider.Enabled) { var lowestLimb = FindLowestLimb(); Collider.SetTransform(new Vector2( Collider.SimPosition.X, Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)), 0.0f); Collider.Enabled = true; } ResetPullJoints(); if (strongestImpact > 0.0f) { character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f); strongestImpact = 0.0f; } if (inWater && !forceStanding) { Collider.FarseerBody.FixedRotation = false; UpdateSineAnim(deltaTime); } else if (CanEnterSubmarine && (currentHull != null || forceStanding) && CurrentGroundedParams != null) { //rotate collider back upright float standAngle = dir == Direction.Right ? CurrentGroundedParams.ColliderStandAngleInRadians : -CurrentGroundedParams.ColliderStandAngleInRadians; if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f) { Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f; Collider.FarseerBody.FixedRotation = false; } else { Collider.FarseerBody.FixedRotation = true; } UpdateWalkAnim(deltaTime); } //don't flip or drag when simply physics is enabled if (SimplePhysicsEnabled) { return; } if (!character.IsRemotePlayer && (character.AIController == null || character.AIController.CanFlip)) { if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror)) { if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.2f) { TargetDir = Direction.Right; } else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.2f) { TargetDir = Direction.Left; } } else { float refAngle = 0.0f; Limb refLimb = GetLimb(LimbType.Head); if (refLimb == null) { refAngle = CurrentAnimationParams.TorsoAngleInRadians; refLimb = GetLimb(LimbType.Torso); } else { refAngle = CurrentAnimationParams.HeadAngleInRadians; } float rotation = refLimb.Rotation; if (!float.IsNaN(refAngle)) { rotation -= refAngle * Dir; } rotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(rotation)); if (rotation < 0.0f) { rotation += 360; } if (rotation > 20 && rotation < 160) { TargetDir = Direction.Left; } else if (rotation > 200 && rotation < 340) { TargetDir = Direction.Right; } } } if (character.SelectedCharacter != null) { DragCharacter(character.SelectedCharacter, deltaTime); } if (!CurrentFishAnimation.Flip || IsStuck) { return; } if (character.AIController != null && !character.AIController.CanFlip) { return; } flipCooldown -= deltaTime; if (TargetDir != Direction.None && TargetDir != dir) { flipTimer += deltaTime; if ((flipTimer > 0.5f && flipCooldown <= 0.0f) || character.IsRemotePlayer) { Flip(); if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror)) { Mirror(); } flipTimer = 0.0f; flipCooldown = 1.0f; } } else { flipTimer = 0.0f; } }
void UpdateWalkAnim(float deltaTime) { movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f); Collider.LinearVelocity = new Vector2( movement.X, Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y); //limbs are disabled when simple physics is enabled, no need to move them if (SimplePhysicsEnabled) { return; } Vector2 colliderBottom = GetColliderBottom(); float movementAngle = 0.0f; var mainLimb = MainLimb; float mainLimbAngle = (mainLimb.type == LimbType.Torso ? TorsoAngle ?? 0 : HeadAngle ?? 0) * Dir; while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { movementAngle += MathHelper.TwoPi; } while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { movementAngle -= MathHelper.TwoPi; } float stepLift = TargetMovement.X == 0.0f ? 0 : (float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100); float limpAmount = character.GetLegPenalty(); if (limpAmount > 0) { float walkPosX = (float)Math.Cos(WalkPos); //make the footpos oscillate when limping limpAmount = Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f) * Dir; } Limb torso = GetLimb(LimbType.Torso); if (torso != null) { if (TorsoAngle.HasValue) { SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque); } if (TorsoPosition.HasValue) { Vector2 pos = colliderBottom + new Vector2(limpAmount, TorsoPosition.Value + stepLift); if (torso != mainLimb) { pos.X = torso.SimPosition.X; } torso.MoveToPos(pos, TorsoMoveForce); torso.PullJointEnabled = true; torso.PullJointWorldAnchorB = pos; } } Limb head = GetLimb(LimbType.Head); if (head != null) { if (HeadAngle.HasValue) { SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque); } if (HeadPosition.HasValue) { Vector2 pos = colliderBottom + new Vector2(limpAmount, HeadPosition.Value + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier); if (head != mainLimb) { pos.X = head.SimPosition.X; } head.MoveToPos(pos, HeadMoveForce); head.PullJointEnabled = true; head.PullJointWorldAnchorB = pos; } } if (TailAngle.HasValue) { var tail = GetLimb(LimbType.Tail); if (tail != null) { SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque); } } float prevWalkPos = WalkPos; WalkPos -= mainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f); Vector2 transformedStepSize = Vector2.Zero; if (Math.Abs(TargetMovement.X) > 0.01f) { transformedStepSize = new Vector2( (float)Math.Cos(WalkPos) * StepSize.Value.X * 3.0f, (float)Math.Sin(WalkPos) * StepSize.Value.Y * 2.0f); } foreach (Limb limb in Limbs) { if (limb.IsSevered) { continue; } if (Math.Abs(limb.Params.ConstantTorque) > 0) { limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); } switch (limb.type) { case LimbType.LeftFoot: case LimbType.RightFoot: Vector2 footPos = new Vector2(limb.SimPosition.X, colliderBottom.Y); if (limb.RefJointIndex > -1) { if (LimbJoints.Length <= limb.RefJointIndex) { DebugConsole.ThrowError($"Reference joint index {limb.RefJointIndex} is out of array. This is probably due to a missing joint. If you just deleted a joint, don't do that without first removing the reference joint indices from the limbs. If this is not the case, please ensure that you have defined the index to the right joint."); } else { footPos.X = LimbJoints[limb.RefJointIndex].WorldAnchorA.X; } } footPos.X += limb.StepOffset.X * Dir; footPos.Y += limb.StepOffset.Y; bool playFootstepSound = false; if (limb.type == LimbType.LeftFoot) { if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0) { playFootstepSound = true; } limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( transformedStepSize.X + movement.X * 0.1f, (transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f); limb.MoveToPos(limb.DebugTargetPos, FootMoveForce); } else if (limb.type == LimbType.RightFoot) { if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0) { playFootstepSound = true; } limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f; limb.DebugTargetPos = footPos + new Vector2( -transformedStepSize.X + movement.X * 0.1f, (-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f); limb.MoveToPos(limb.DebugTargetPos, FootMoveForce); } if (playFootstepSound) { #if CLIENT PlayImpactSound(limb); #endif } if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.Params.ID)) { SmoothRotateWithoutWrapping(limb, movementAngle + CurrentGroundedParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque); } break; case LimbType.LeftLeg: case LimbType.RightLeg: if (Math.Abs(CurrentGroundedParams.LegTorque) > 0) { limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir); } break; } } }
void UpdateSineAnim(float deltaTime) { if (CurrentSwimParams == null) { return; } movement = TargetMovement; if (movement.LengthSquared() > 0.00001f) { Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f); } //limbs are disabled when simple physics is enabled, no need to move them if (SimplePhysicsEnabled) { return; } MainLimb.PullJointEnabled = true; //MainLimb.PullJointWorldAnchorB = Collider.SimPosition; if (movement.LengthSquared() < 0.00001f) { WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5); MainLimb.PullJointWorldAnchorB = Collider.SimPosition; return; } Vector2 transformedMovement = reverse ? -movement : movement; float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2; float mainLimbAngle = 0; if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue) { mainLimbAngle = TorsoAngle.Value; } else if (MainLimb.type == LimbType.Head && HeadAngle.HasValue) { mainLimbAngle = HeadAngle.Value; } mainLimbAngle *= Dir; while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { movementAngle += MathHelper.TwoPi; } while (MainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { movementAngle -= MathHelper.TwoPi; } if (CurrentSwimParams.RotateTowardsMovement) { Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque); if (TorsoAngle.HasValue) { Limb torso = GetLimb(LimbType.Torso); if (torso != null) { SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, MainLimb, TorsoTorque); } } if (HeadAngle.HasValue) { Limb head = GetLimb(LimbType.Head); if (head != null) { SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, MainLimb, HeadTorque); } } if (TailAngle.HasValue) { Limb tail = GetLimb(LimbType.Tail); if (tail != null) { SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, MainLimb, TailTorque); } } } else { movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; if (reverse) { movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); } if (MainLimb.type == LimbType.Head && HeadAngle.HasValue) { Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque); } else if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue) { Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque); } if (TorsoAngle.HasValue) { Limb torso = GetLimb(LimbType.Torso); torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque); } if (HeadAngle.HasValue) { Limb head = GetLimb(LimbType.Head); head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque); } if (TailAngle.HasValue) { Limb tail = GetLimb(LimbType.Tail); tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque); } } var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude); if (waveLength > 0 && waveAmplitude > 0) { WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); } foreach (var limb in Limbs) { switch (limb.type) { case LimbType.LeftFoot: case LimbType.RightFoot: if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID)) { SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.limbParams.ID] * Dir, MainLimb, FootTorque); } break; case LimbType.Tail: if (waveLength > 0 && waveAmplitude > 0) { float waveRotation = (float)Math.Sin(WalkPos); limb.body.ApplyTorque(waveRotation * limb.Mass * CurrentSwimParams.TailTorque * waveAmplitude); } break; } } for (int i = 0; i < Limbs.Length; i++) { if (Limbs[i].SteerForce <= 0.0f) { continue; } Vector2 pullPos = Limbs[i].PullJointWorldAnchorA; Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos); } Vector2 mainLimbDiff = MainLimb.PullJointWorldAnchorB - MainLimb.SimPosition; if (CurrentSwimParams.UseSineMovement) { MainLimb.PullJointWorldAnchorB = Vector2.SmoothStep( MainLimb.PullJointWorldAnchorB, Collider.SimPosition, mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos))); } else { //MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointWorldAnchorB = Vector2.Lerp( MainLimb.PullJointWorldAnchorB, Collider.SimPosition, mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f); } floorY = Limbs[0].SimPosition.Y; }
void UpdateDying(float deltaTime) { if (deathAnimDuration <= 0.0f) { return; } float noise = (PerlinNoise.GetPerlin(WalkPos * 0.002f, WalkPos * 0.003f) - 0.5f) * 5.0f; float animStrength = (1.0f - deathAnimTimer / deathAnimDuration); Limb head = GetLimb(LimbType.Head); if (head != null && head.IsSevered) { return; } Limb tail = GetLimb(LimbType.Tail); if (head != null && !head.IsSevered) { head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } if (tail != null && !tail.IsSevered) { tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (Math.Sin(WalkPos) + noise)) * 30.0f * animStrength); } WalkPos += deltaTime * 10.0f * animStrength; Vector2 centerOfMass = GetCenterOfMass(); foreach (Limb limb in Limbs) { if (limb.IsSevered) { continue; } #if CLIENT if (limb.LightSource != null) { limb.LightSource.Color = Color.Lerp(limb.InitialLightSourceColor, Color.TransparentBlack, deathAnimTimer / deathAnimDuration); if (limb.InitialLightSpriteAlpha.HasValue) { limb.LightSource.OverrideLightSpriteAlpha = MathHelper.Lerp(limb.InitialLightSpriteAlpha.Value, 0.0f, deathAnimTimer / deathAnimDuration); } } #endif if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered || !limb.body.Enabled) { continue; } if (limb.Mass <= 0.0f) { string errorMsg = "Creature death animation error: invalid limb mass on character \"" + character.SpeciesName + "\" (type: " + limb.type + ", mass: " + limb.Mass + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidMass" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } Vector2 diff = (centerOfMass - limb.SimPosition); if (!MathUtils.IsValid(diff)) { string errorMsg = "Creature death animation error: invalid diff (center of mass: " + centerOfMass + ", limb position: " + limb.SimPosition + ")"; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("FishAnimController.UpdateDying:InvalidDiff" + character.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); deathAnimTimer = deathAnimDuration; return; } limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength, maxVelocity: 10.0f); } }
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { if (GameMain.Server == null) { return; } if (extraData != null) { switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: msg.WriteRangedInteger(0, 0, 5); msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Control: msg.WriteRangedInteger(1, 0, 5); Client owner = (Client)extraData[1]; msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; case NetEntityEvent.Type.Status: msg.WriteRangedInteger(2, 0, 5); WriteStatus(msg); break; case NetEntityEvent.Type.UpdateSkills: msg.WriteRangedInteger(3, 0, 5); if (Info?.Job == null) { msg.Write((byte)0); } else { msg.Write((byte)Info.Job.Skills.Count); foreach (Skill skill in Info.Job.Skills) { msg.Write(skill.Identifier); msg.Write(skill.Level); } } break; case NetEntityEvent.Type.ExecuteAttack: Limb attackLimb = extraData[1] as Limb; UInt16 targetEntityID = (UInt16)extraData[2]; int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0; msg.WriteRangedInteger(4, 0, 5); msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb))); msg.Write(targetEntityID); msg.Write((byte)targetLimbIndex); break; case NetEntityEvent.Type.AssignCampaignInteraction: msg.WriteRangedInteger(5, 0, 5); msg.Write((byte)CampaignInteractionType); break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; } msg.WritePadBits(); } else { msg.Write(ID); IWriteMessage tempBuffer = new WriteOnlyMessage(); if (this == c.Character) { tempBuffer.Write(true); if (LastNetworkUpdateID < memInput.Count + 1) { tempBuffer.Write((UInt16)0); } else { tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1)); } } else { tempBuffer.Write(false); bool aiming = false; bool use = false; bool attack = false; bool shoot = false; if (IsRemotePlayer) { aiming = dequeuedInput.HasFlag(InputNetFlags.Aim); use = dequeuedInput.HasFlag(InputNetFlags.Use); attack = dequeuedInput.HasFlag(InputNetFlags.Attack); shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot); } else if (keys != null) { aiming = keys[(int)InputType.Aim].GetHeldQueue; use = keys[(int)InputType.Use].GetHeldQueue; attack = keys[(int)InputType.Attack].GetHeldQueue; shoot = keys[(int)InputType.Shoot].GetHeldQueue; networkUpdateSent = true; } tempBuffer.Write(aiming); tempBuffer.Write(shoot); tempBuffer.Write(use); if (AnimController is HumanoidAnimController) { tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching); } tempBuffer.Write(attack); Vector2 relativeCursorPos = cursorPosition - AimRefPosition; tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI))); tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated); tempBuffer.Write(AnimController.Dir > 0.0f); } if (SelectedCharacter != null || SelectedConstruction != null) { tempBuffer.Write(true); tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID); if (SelectedCharacter != null) { tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR); } } else { tempBuffer.Write(false); } tempBuffer.Write(SimPosition.X); tempBuffer.Write(SimPosition.Y); float MaxVel = NetConfig.MaxPhysicsBodyVelocity; AnimController.Collider.LinearVelocity = new Vector2( MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel), MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel)); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12); tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12); bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled; tempBuffer.Write(fixedRotation); if (!fixedRotation) { tempBuffer.Write(AnimController.Collider.Rotation); float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8); tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8); } bool writeStatus = healthUpdateTimer <= 0.0f; tempBuffer.Write(writeStatus); if (writeStatus) { WriteStatus(tempBuffer); HealthUpdatePending = false; } tempBuffer.WritePadBits(); msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } }
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One) { Params = jointParams; this.ragdoll = ragdoll; LoadParams(); }
public static void UpdateAll(float deltaTime) { UpdateAllProjSpecific(deltaTime); DelayedEffect.Update(deltaTime); for (int i = DurationList.Count - 1; i >= 0; i--) { DurationListElement element = DurationList[i]; if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets)) { DurationList.RemoveAt(i); continue; } element.Targets.RemoveAll(t => (t is Entity entity && entity.Removed) || (t is Limb limb && (limb.character == null || limb.character.Removed))); if (element.Targets.Count == 0) { DurationList.RemoveAt(i); continue; } foreach (ISerializableEntity target in element.Targets) { for (int n = 0; n < element.Parent.propertyNames.Length; n++) { if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(element.Parent.propertyNames[n], out SerializableProperty property)) { continue; } element.Parent.ApplyToProperty(target, property, element.Parent.propertyEffects[n], CoroutineManager.UnscaledDeltaTime); } foreach (Affliction affliction in element.Parent.Afflictions) { Affliction multipliedAffliction = affliction; if (!element.Parent.disableDeltaTime) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); } if (target is Character character) { character.AddDamage(character.WorldPosition, new List <Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false); } else if (target is Limb limb) { limb.character.DamageLimb(limb.WorldPosition, limb, new List <Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f); } } foreach (Pair <string, float> reduceAffliction in element.Parent.ReduceAffliction) { Limb targetLimb = null; Character targetCharacter = null; if (target is Character character) { targetCharacter = character; } else if (target is Limb limb) { targetLimb = limb; targetCharacter = limb.character; } if (targetCharacter != null) { float prevVitality = targetCharacter.Vitality; targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime); #if SERVER GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.Parent.user, prevVitality - targetCharacter.Vitality); #endif } } } element.Timer -= deltaTime; if (element.Timer > 0.0f) { continue; } DurationList.Remove(element); } }
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) { return; } //long range for the broad distance check, because large characters may still be in range even if their collider isn't float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f); foreach (Character c in Character.CharacterList) { if (!c.Enabled || Math.Abs(c.WorldPosition.X - worldPosition.X) > broadRange || Math.Abs(c.WorldPosition.Y - worldPosition.Y) > broadRange) { continue; } if (onlyInside && c.Submarine == null) { continue; } else if (onlyOutside && c.Submarine != null) { continue; } Vector2 explosionPos = worldPosition; if (c.Submarine != null) { explosionPos -= c.Submarine.Position; } Hull hull = Hull.FindHull(explosionPos, null, false); bool underWater = hull == null || explosionPos.Y < hull.Surface; explosionPos = ConvertUnits.ToSimUnits(explosionPos); Dictionary <Limb, float> distFactors = new Dictionary <Limb, float>(); Dictionary <Limb, float> damages = new Dictionary <Limb, float>(); List <Affliction> modifiedAfflictions = new List <Affliction>(); foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; } float dist = Vector2.Distance(limb.WorldPosition, worldPosition); //calculate distance from the "outer surface" of the physics body //doesn't take the rotation of the limb into account, but should be accurate enough for this purpose float limbRadius = limb.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius)); if (dist > attack.Range) { continue; } float distFactor = 1.0f - dist / attack.Range; //solid obstacles between the explosion and the limb reduce the effect of the explosion if (!ignoreCover) { distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition); } distFactors.Add(limb, distFactor); modifiedAfflictions.Clear(); foreach (Affliction affliction in attack.Afflictions.Keys) { //previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions) //that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters //have lots of limbs, they tended to only take a fraction of the damage they should //now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10)); } c.LastDamageSource = damageSource; if (attacker == null) { if (damageSource is Item item) { attacker = item.GetComponent <Projectile>()?.User; if (attacker == null) { attacker = item.GetComponent <MeleeWeapon>()?.User; } } } //use a position slightly from the limb's position towards the explosion //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 dir = worldPosition - limb.WorldPosition; Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f; AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); damages.Add(limb, attackResult.Damage); if (attack.StatusEffects != null && attack.StatusEffects.Any()) { attack.SetUser(attacker); var statusEffectTargets = new List <ISerializableEntity>() { c, limb }; foreach (StatusEffect statusEffect in attack.StatusEffects) { statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets); statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets); } } if (limb.WorldPosition != worldPosition && !MathUtils.NearlyEqual(force, 0.0f)) { Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition); if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); } Vector2 impulse = limbDiff * distFactor * force; Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius; limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f); } } if (c == Character.Controlled && !c.IsDead && playTinnitus) { Limb head = c.AnimController.GetLimb(LimbType.Head); if (head != null && damages.TryGetValue(head, out float headDamage) && headDamage > 0.0f && distFactors.TryGetValue(head, out float headFactor)) { PlayTinnitusProjSpecific(headFactor); } } //sever joints if (attack.SeverLimbsProbability > 0.0f) { foreach (Limb limb in c.AnimController.Limbs) { if (limb.character.Removed || limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; } if (distFactors.TryGetValue(limb, out float distFactor)) { if (damages.TryGetValue(limb, out float damage)) { c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } } } }