2 using FarseerPhysics.Dynamics;
3 using FarseerPhysics.Dynamics.Contacts;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
21 private float reloadTimer;
25 private readonly HashSet<Entity> hitTargets =
new HashSet<Entity>();
27 private readonly Queue<Fixture> impactQueue =
new Queue<Fixture>();
31 [
Serialize(0.0f,
IsPropertySaveable.No, description:
"An estimation of how close the item has to be to the target for it to hit. Used by AI characters to determine when they're close enough to hit a target.")]
34 get {
return ConvertUnits.ToDisplayUnits(range); }
35 set { range = ConvertUnits.ToSimUnits(value); }
38 [
Serialize(0.5f,
IsPropertySaveable.No, description:
"How long the user has to wait before they can hit with the weapon again (in seconds).")]
41 get {
return reload; }
42 set { reload = Math.Max(0.0f, value); }
52 [
Serialize(
false,
IsPropertySaveable.No, description:
"Disable to make the weapon ignore all hit effects when it collides with walls, doors, or other items.")]
68 public bool Hitting {
get {
return hitting; } }
78 foreach (var subElement
in element.Elements())
80 if (!subElement.Name.ToString().Equals(
"attack", StringComparison.OrdinalIgnoreCase)) {
continue; }
88 PreferredContainedItems = element.GetAttributeIdentifierArray(
"preferredcontaineditems", Array.Empty<Identifier>()).ToImmutableHashSet();
93 base.Equip(character);
95 const float forcedDelayOnEquip = 1.0f;
96 reloadTimer = Math.Max(Math.Min(reload, forcedDelayOnEquip), reloadTimer);
102 if (character ==
null || reloadTimer > 0.0f) {
return false; }
109 foreach (
Item heldItem
in character.HeldItems)
111 var otherWeapon = heldItem.GetComponent<
MeleeWeapon>();
112 if (otherWeapon ==
null) {
continue; }
113 if (otherWeapon.hitting) {
return false; }
120 ActivateNearbySleepingCharacters();
121 reloadTimer = reload;
122 reloadTimer /= 1f + character.GetStatValue(
StatTypes.MeleeAttackSpeed);
124 character.AnimController.LockFlipping();
127 item.
body.
FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
132 if (
Swing && !character.AnimController.InWater)
134 foreach (
Limb l
in character.AnimController.Limbs)
150 force = Vector2.Zero;
181 base.Drop(dropper, setTransform);
201 while (impactQueue.Count > 0)
203 var impact = impactQueue.Dequeue();
204 HandleImpact(impact);
207 if (
picker ==
null) {
return; }
208 reloadTimer -= deltaTime;
229 hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
245 hitPos -= deltaTime * 15f;
254 if (hitPos < -MathHelper.Pi)
264 private void ActivateNearbySleepingCharacters()
275 float hitRange = 2.0f;
276 if (Vector2.DistanceSquared(limb.SimPosition,
item.
SimPosition) < hitRange * hitRange)
285 private void SetUser(Character character)
287 if (
User == character) {
return; }
293 private void EndHit()
301 private void RestoreCollision()
311 private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
315 impactQueue.Enqueue(f2);
319 contact.GetWorldManifold(out Vector2 normal, out var points);
324 collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
325 allowInsideFixture:
true,
326 customPredicate: (Fixture fixture) => { return fixture.CollidesWith.HasFlag(Physics.CollisionItem) && fixture.Body != f2.Body; }) !=
null)
331 if (f2.Body.UserData is Limb targetLimb)
333 if (targetLimb.IsSevered || targetLimb.character ==
null || targetLimb.character ==
User) {
return false; }
334 if (targetLimb.character.IgnoreMeleeWeapons) {
return false; }
335 var targetCharacter = targetLimb.character;
336 if (targetCharacter ==
picker) {
return false; }
339 if (hitTargets.Contains(targetCharacter)) {
return false; }
343 if (hitTargets.Any(t => t is Character)) {
return false; }
345 hitTargets.Add(targetCharacter);
347 else if (f2.Body.UserData is Character targetCharacter)
349 if (targetCharacter ==
picker || targetCharacter ==
User) {
return false; }
350 if (targetCharacter.IgnoreMeleeWeapons) {
return false; }
351 targetLimb = targetCharacter.AnimController.GetLimb(
LimbType.Torso);
354 if (hitTargets.Contains(targetCharacter)) {
return false; }
358 if (hitTargets.Any(t => t is Character)) {
return false; }
360 hitTargets.Add(targetCharacter);
364 if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
368 if (hitTargets.Contains(targetStructure)) {
return true; }
372 if (hitTargets.Any(t => t is Structure)) {
return true; }
374 hitTargets.Add(targetStructure);
376 else if ((f2.Body.UserData as
Item ?? f2.UserData as
Item) is
Item targetItem)
380 if (hitTargets.Contains(targetItem)) {
return true; }
384 if (hitTargets.Any(t => t is
Item)) {
return true; }
386 hitTargets.Add(targetItem);
388 else if (f2.Body.UserData is
Holdable holdable && holdable.CanPush)
390 if (holdable.Item.GetRootInventoryOwner() ==
User) {
return false; }
391 hitTargets.Add(holdable.Item);
399 impactQueue.Enqueue(f2);
404 private System.Text.StringBuilder serverLogger;
405 private void HandleImpact(Fixture targetFixture)
407 var target = targetFixture.Body;
420 Limb targetLimb = target.UserData as Limb;
423 Item targetItem = target.UserData as
Item ?? targetFixture.UserData as
Item;
424 Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
425 GameMain.LuaCs.Hook.Call(
"meleeWeapon.handleImpact",
this, target);
430 if (targetLimb !=
null)
432 if (targetLimb.character.Removed) {
return; }
433 targetLimb.character.LastDamageSource =
item;
436 else if (targetCharacter !=
null)
438 if (targetCharacter.Removed) {
return; }
439 targetCharacter.LastDamageSource =
item;
442 else if (targetStructure !=
null)
444 if (targetStructure.Removed) {
return; }
447 else if (targetItem !=
null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
449 if (targetItem.Removed) {
return; }
452 if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar &&
Character.Controlled !=
null &&
455 Character.Controlled.UpdateHUDProgressBar(targetItem,
456 targetItem.WorldPosition,
457 targetItem.Condition / targetItem.MaxCondition,
458 emptyColor: GUIStyle.HealthBarColorLow,
459 fullColor: GUIStyle.HealthBarColorHigh,
460 textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name :
string.Empty);
464 else if (target.UserData is
Holdable holdable && holdable.CanPush)
466 if (holdable.Item.Removed) {
return; }
478 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient) {
return; }
485 if (GameMain.NetworkMember is { IsServer: true } server && targetEntity !=
null)
487 server.CreateEntityEvent(
item,
new Item.
ApplyStatusEffectEventData(conditionalActionType, targetItemComponent:
this, targetCharacter, targetLimb, useTarget: targetEntity));
489 serverLogger ??=
new System.Text.StringBuilder();
490 serverLogger.Clear();
491 serverLogger.Append($
"{picker?.LogName} used {item.Name}");
494 serverLogger.Append($
"({string.Join(",
", item.ContainedItems.Select(i => i?.Name))})");
497 if (targetCharacter !=
null)
499 targetName = targetCharacter.LogName;
501 else if (targetItem !=
null)
503 targetName = targetItem.Name;
505 else if (targetStructure !=
null)
507 targetName = targetStructure.Name;
511 targetName = targetEntity.ToString();
513 serverLogger.Append($
" on {targetName}.");
515 Networking.GameServer.Log(serverLogger.ToString(), Networking.ServerLog.MessageType.Attack);
518 if (targetEntity !=
null)
520 ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
521 ApplyStatusEffects(
ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
526 Entity.Spawner.AddItemToRemoveQueue(
item);
void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 itemPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle=0.0f, bool aimMelee=false, Vector2? targetPos=null)
virtual Vector2 AimSourceSimPos
void LockFlipping(float time=0.2f)
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
float DamageMultiplier
Used for multiplying all the damage.
void SetUser(Character user)
AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound=true, PhysicsBody sourceBody=null, Limb sourceLimb=null)
float GetStatValue(StatTypes statType, bool includeSaved=true)
static readonly List< Character > CharacterList
bool IsKeyDown(InputType inputType)
readonly AnimController AnimController
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
virtual Vector2 WorldPosition
static readonly List< Item > DraggingItems
override Vector2? SimPosition
bool IsShootable
Should the item's Use method be called with the "Use" or with the "Shoot" key?
override void FlipX(bool relativeToSub)
Flip the entity horizontally
bool RequireAimToUse
If true, the user has to hold the "aim" key before use is registered. False by default.
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
IEnumerable< Item > ContainedItems
float GetQualityModifier(Quality.StatType statType)
bool UsageDisabledByRangedWeapon(Character character)
Holdable(Item item, ContentXElement element)
void UpdateSwingPos(float deltaTime, out Vector2 swingPos)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
float DegreeOfSuccess(Character character)
Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
override bool Use(float deltaTime, Character character=null)
readonly ImmutableHashSet< Identifier > PreferredContainedItems
Defines items that boost the weapon functionality, like battery cell for stun batons.
override void Equip(Character character)
MeleeWeapon(Item item, ContentXElement element)
override void Update(float deltaTime, Camera cam)
override bool SecondaryUse(float deltaTime, Character character=null)
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
override void UpdateBroken(float deltaTime, Camera cam)
void ApplyLinearImpulse(Vector2 impulse)
Category CollisionCategories
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!