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;
177 base.Drop(dropper, setTransform);
199 while (impactQueue.Count > 0)
201 var impact = impactQueue.Dequeue();
202 HandleImpact(impact);
205 if (
picker ==
null) {
return; }
206 reloadTimer -= deltaTime;
227 hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
243 hitPos -= deltaTime * 15f;
252 if (hitPos < -MathHelper.Pi)
265 private void ActivateNearbySleepingCharacters()
276 float hitRange = 2.0f;
277 if (Vector2.DistanceSquared(limb.SimPosition,
item.
SimPosition) < hitRange * hitRange)
286 private void SetUser(Character character)
288 if (
User == character) {
return; }
294 private void RestoreCollision()
304 private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
308 impactQueue.Enqueue(f2);
312 contact.GetWorldManifold(out Vector2 normal, out var points);
317 collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
318 allowInsideFixture:
true,
319 customPredicate: (Fixture fixture) => { return fixture.CollidesWith.HasFlag(Physics.CollisionItem) && fixture.Body != f2.Body; }) !=
null)
324 if (f2.Body.UserData is Limb targetLimb)
326 if (targetLimb.IsSevered || targetLimb.character ==
null || targetLimb.character ==
User) {
return false; }
327 if (targetLimb.character.IgnoreMeleeWeapons) {
return false; }
328 var targetCharacter = targetLimb.character;
329 if (targetCharacter ==
picker) {
return false; }
332 if (hitTargets.Contains(targetCharacter)) {
return false; }
336 if (hitTargets.Any(t => t is Character)) {
return false; }
338 hitTargets.Add(targetCharacter);
340 else if (f2.Body.UserData is Character targetCharacter)
342 if (targetCharacter ==
picker || targetCharacter ==
User) {
return false; }
343 if (targetCharacter.IgnoreMeleeWeapons) {
return false; }
344 targetLimb = targetCharacter.AnimController.GetLimb(
LimbType.Torso);
347 if (hitTargets.Contains(targetCharacter)) {
return false; }
351 if (hitTargets.Any(t => t is Character)) {
return false; }
353 hitTargets.Add(targetCharacter);
357 if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
361 if (hitTargets.Contains(targetStructure)) {
return true; }
365 if (hitTargets.Any(t => t is Structure)) {
return true; }
367 hitTargets.Add(targetStructure);
369 else if ((f2.Body.UserData as
Item ?? f2.UserData as
Item) is
Item targetItem)
373 if (hitTargets.Contains(targetItem)) {
return true; }
377 if (hitTargets.Any(t => t is
Item)) {
return true; }
379 hitTargets.Add(targetItem);
381 else if (f2.Body.UserData is
Holdable holdable && holdable.CanPush)
383 hitTargets.Add(holdable.Item);
391 impactQueue.Enqueue(f2);
396 private System.Text.StringBuilder serverLogger;
397 private void HandleImpact(Fixture targetFixture)
399 var target = targetFixture.Body;
412 Limb targetLimb = target.UserData as Limb;
415 Item targetItem = target.UserData as
Item ?? targetFixture.UserData as
Item;
416 Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
417 GameMain.LuaCs.Hook.Call(
"meleeWeapon.handleImpact",
this, target);
422 if (targetLimb !=
null)
424 if (targetLimb.character.Removed) {
return; }
425 targetLimb.character.LastDamageSource =
item;
428 else if (targetCharacter !=
null)
430 if (targetCharacter.Removed) {
return; }
431 targetCharacter.LastDamageSource =
item;
434 else if (targetStructure !=
null)
436 if (targetStructure.Removed) {
return; }
439 else if (targetItem !=
null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
441 if (targetItem.Removed) {
return; }
444 if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar &&
Character.Controlled !=
null &&
447 Character.Controlled.UpdateHUDProgressBar(targetItem,
448 targetItem.WorldPosition,
449 targetItem.Condition / targetItem.MaxCondition,
450 emptyColor: GUIStyle.HealthBarColorLow,
451 fullColor: GUIStyle.HealthBarColorHigh,
452 textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name :
string.Empty);
456 else if (target.UserData is
Holdable holdable && holdable.CanPush)
458 if (holdable.Item.Removed) {
return; }
470 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient) {
return; }
477 if (GameMain.NetworkMember is { IsServer: true } server && targetEntity !=
null)
479 server.CreateEntityEvent(
item,
new Item.
ApplyStatusEffectEventData(conditionalActionType, targetItemComponent:
null, targetCharacter, targetLimb, useTarget: targetEntity));
481 serverLogger ??=
new System.Text.StringBuilder();
482 serverLogger.Clear();
483 serverLogger.Append($
"{picker?.LogName} used {item.Name}");
486 serverLogger.Append($
"({string.Join(",
", item.ContainedItems.Select(i => i?.Name))})");
489 if (targetCharacter !=
null)
491 targetName = targetCharacter.LogName;
493 else if (targetItem !=
null)
495 targetName = targetItem.Name;
497 else if (targetStructure !=
null)
499 targetName = targetStructure.Name;
503 targetName = targetEntity.ToString();
505 serverLogger.Append($
" on {targetName}.");
507 Networking.GameServer.Log(serverLogger.ToString(), Networking.ServerLog.MessageType.Attack);
510 if (targetEntity !=
null)
512 ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
513 ApplyStatusEffects(
ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
518 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.