Skip to content

Commit 2c7b540

Browse files
authored
Initial Archery Overhaul (#194)
* Implemented charge releasing for bows. * On-charge air-hovering for archers. * Renamed namespace to Archery * Fixed & adjusted hovering. * Tweaks to hovering. * Added velocity recoil to hover-shots. * Fixed previous commit. * Rewrote hover shots with input grace periods. * Basic charging arrow rendering. * Fixed archery changes breaking melee collision. * Reduced hovershot velocity. * Made bow charging linear, for now? * Made velocity recoil take charge progress into account. * Reintroduced firing delays in primary use. * Improved the arrow animation. * Added some crappy screenshake to the charging. * Improved bow charging screenshake. * Fixed arrows not using exact weapon rotation. * Split Bows with a sound-only Repeater overhaul. * Removed arrow animations from repeaters. * Fixed item uses occuring on world joins. * Support most arrow replacements in rendering. * Support multi-framed arrows in rendering. * Don't do anything with channeled bows. * Workarounds for invalid width/height values on vanilla bows. * Excluded Evertide from bow overhauls. * Render 5 arrows for Tsunami. * Cleanups. * Added Combat Info tooltips to bows. * Fixed incorrect hovershot ground logic. * Changelog entry. * Fixed various multiplayer desync.
1 parent 4c11309 commit 2c7b540

25 files changed

+803
-93
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
- Visual Recoil - The slight offset in weapon's rotation after it's used.
3737
- Muzzleflashes - The weapons' fire breath.
3838
- Screen-shake - Self-explanatory.
39+
- Implemented an initial Archery Overhaul:
40+
- Bows can now again be manually charged using right click. This charge can be released at any time, scaling damage, knockback and projectile speed according to the charge progress.
41+
- Reintroduced arrow charging animations, improved much with fancy easing functions.
42+
- Introduced experimental (experimental means fun) "Hover-shots" - Start a bow charge while holding down the Jump button to hover over enemies. Release the string to receive double-jump-like recoil velocity.
43+
- Bows' primary use now has a short delay before firing. The after-firing use cooldown is shortened as a compensation, so this shouldn't affect DPS.
3944
- Implemented tree falling animations. Difference from 1.3 Terraria Overhaul versions are as follows:
4045
- Stumps are now automatically destroyed after a tree falls down. Toggleable.
4146
- Rendering is done with vanilla callsites, should be compatible with all mods' trees out of the box.
@@ -71,6 +76,9 @@
7176
- Fixed issue [#177](https://github.com/Mirsario/TerrariaOverhaul/issues/177) (Explosives ignore knockback resistance).
7277
- Fixed issue [#124](https://github.com/Mirsario/TerrariaOverhaul/issues/124) (Unable to pet cats & dogs).
7378

79+
### Netcode
80+
- Fixed some cases of desynchronization within power attacks.
81+
7482
### Optimizations
7583
- Crosshair impulse registration no longer causes any heap allocations.
7684

Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
using Terraria;
22
using Terraria.Audio;
33
using Terraria.ID;
4-
using TerrariaOverhaul.Common.Camera;
5-
using TerrariaOverhaul.Common.Charging;
6-
using TerrariaOverhaul.Common.Crosshairs;
7-
using TerrariaOverhaul.Common.Items;
8-
using TerrariaOverhaul.Core.ItemComponents;
9-
using TerrariaOverhaul.Core.ItemOverhauls;
10-
using TerrariaOverhaul.Utilities;
114

12-
namespace TerrariaOverhaul.Common.ModEntities.Items.Overhauls;
5+
namespace TerrariaOverhaul.Common.Archery;
136

14-
public partial class Bow : ItemOverhaul
7+
public static class ArcheryWeapons
158
{
9+
public enum Kind
10+
{
11+
Undefined,
12+
Bow,
13+
Repeater,
14+
}
15+
1616
public static readonly SoundStyle FireSound = new($"{nameof(TerrariaOverhaul)}/Assets/Sounds/Items/Bows/BowFire", 4) {
1717
Volume = 0.375f,
1818
PitchVariance = 0.2f,
@@ -29,50 +29,49 @@ public partial class Bow : ItemOverhaul
2929
MaxInstances = 3,
3030
};
3131

32-
public override bool ShouldApplyItemOverhaul(Item item)
32+
public static bool IsArcheryWeapon(Item item, out Kind kind)
3333
{
34+
kind = Kind.Undefined;
35+
3436
// Ignore weapons that don't shoot, and ones that deal hitbox damage
3537
if (item.shoot <= ProjectileID.None || !item.noMelee) {
3638
return false;
3739
}
3840

41+
// Ignore channeled weapons.
42+
if (item.channel) {
43+
return false;
44+
}
45+
3946
// Ignore weapons that don't shoot arrows.
4047
if (item.useAmmo != AmmoID.Arrow) {
4148
return false;
4249
}
4350

51+
// Ignore specific weapons.
52+
if (item.type == ItemID.FairyQueenRangedItem) {
53+
return false;
54+
}
55+
4456
// Avoid tools and placeables
4557
if (item.pick > 0 || item.axe > 0 || item.hammer > 0 || item.createTile >= TileID.Dirt || item.createWall >= 0) {
4658
return false;
4759
}
4860

61+
kind = GetKind(item);
62+
4963
return true;
5064
}
5165

52-
public override void SetDefaults(Item item)
66+
private static Kind GetKind(Item item)
5367
{
54-
base.SetDefaults(item);
55-
56-
if (item.UseSound == SoundID.Item5) {
57-
item.UseSound = FireSound;
58-
}
59-
60-
item.EnableComponent<ItemPowerAttacks>(c => {
61-
c.ChargeLengthMultiplier = 1.5f; // x2.5
62-
63-
var modifiers = new CommonStatModifiers();
64-
65-
modifiers.ProjectileDamageMultiplier = modifiers.MeleeDamageMultiplier = 1.5f;
66-
modifiers.ProjectileKnockbackMultiplier = modifiers.MeleeKnockbackMultiplier = 1.5f;
67-
modifiers.ProjectileSpeedMultiplier = 2f;
68-
69-
c.StatModifiers.Single = modifiers;
70-
});
71-
72-
if (!Main.dedServ) {
73-
item.EnableComponent<ItemPowerAttackSounds>(c => {
74-
c.Sound = ChargeSound;
75-
});
76-
}
68+
// Tons of items have incorrect width/height values. This is why we can't have nice things.
69+
return item.type switch {
70+
ItemID.PulseBow => Kind.Bow,
71+
ItemID.Tsunami => Kind.Bow,
72+
ItemID.FairyQueenRangedItem => Kind.Bow,
73+
_ when item.width > item.height => Kind.Repeater,
74+
_ => Kind.Bow,
75+
};
7776
}
7877
}

Common/Archery/Bow.cs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.Xna.Framework;
4+
using Terraria;
5+
using Terraria.ID;
6+
using Terraria.ModLoader;
7+
using TerrariaOverhaul.Common.Camera;
8+
using TerrariaOverhaul.Common.Charging;
9+
using TerrariaOverhaul.Common.Items;
10+
using TerrariaOverhaul.Core.ItemComponents;
11+
using TerrariaOverhaul.Core.ItemOverhauls;
12+
using TerrariaOverhaul.Utilities;
13+
14+
namespace TerrariaOverhaul.Common.Archery;
15+
16+
public partial class Bow : ItemOverhaul
17+
{
18+
public override bool ShouldApplyItemOverhaul(Item item)
19+
{
20+
return ArcheryWeapons.IsArcheryWeapon(item, out var kind) && kind == ArcheryWeapons.Kind.Bow;
21+
}
22+
23+
public override void SetDefaults(Item item)
24+
{
25+
base.SetDefaults(item);
26+
27+
if (item.UseSound == SoundID.Item5) {
28+
item.UseSound = ArcheryWeapons.FireSound;
29+
}
30+
31+
item.EnableComponent<ItemPowerAttackHover>(c => {
32+
c.ActivationVelocityRange = new Vector4(
33+
// Minimum X & Y
34+
float.NegativeInfinity, float.NegativeInfinity,
35+
// Maximum X & Y
36+
float.PositiveInfinity, 3.0f
37+
);
38+
c.ControlsVelocityRecoil = true;
39+
});
40+
41+
item.EnableComponent<ItemUseVelocityRecoil>(e => {
42+
e.BaseVelocity = new(15.0f, 15.0f);
43+
e.MaxVelocity = new(6.0f, 8.5f);
44+
}).SetEnabled(item, false);
45+
46+
item.EnableComponent<ItemPowerAttacks>(c => {
47+
c.CanRelease = true;
48+
c.ChargeLengthMultiplier = 2.0f;
49+
50+
var weakest = new CommonStatModifiers {
51+
ProjectileSpeedMultiplier = 0.25f,
52+
};
53+
var strongest = new CommonStatModifiers {
54+
ProjectileDamageMultiplier = 2.0f,
55+
ProjectileKnockbackMultiplier = 2.0f,
56+
ProjectileSpeedMultiplier = 3.0f,
57+
};
58+
59+
c.StatModifiers.Gradient = new(stackalloc Gradient<CommonStatModifiers>.Key[] {
60+
new(0.000f, weakest),
61+
//new(0.750f, strongest),
62+
new(1.000f, strongest),
63+
});
64+
});
65+
66+
item.EnableComponent<ItemPrimaryUseCharging>(c => {
67+
// One third of the vanilla use time is spent on the charge, two thirds remain.
68+
c.UseLengthMultiplier = 2f / 3f;
69+
c.ChargeLengthMultiplier = 1f / 3f;
70+
});
71+
72+
if (!Main.dedServ) {
73+
item.EnableComponent<ItemArrowRendering>(c => {
74+
75+
});
76+
77+
item.EnableComponent<ItemPowerAttackScreenShake>(c => {
78+
static float ScreenShakePowerFunction(float progress)
79+
{
80+
const float StartOffset = 0.00f;
81+
const float MaxPower = 0.15f;
82+
const float PowX = 7f;
83+
84+
return MathHelper.Clamp((MathF.Pow(progress, PowX) * (1f + StartOffset)) - StartOffset, 0f, 1f) * MaxPower;
85+
}
86+
87+
c.ScreenShake = new ScreenShake(ScreenShakePowerFunction, float.PositiveInfinity);
88+
});
89+
90+
item.EnableComponent<ItemPowerAttackSounds>(c => {
91+
c.Sound = ArcheryWeapons.ChargeSound;
92+
c.CancelPlaybackOnEnd = true;
93+
});
94+
}
95+
}
96+
97+
public override void ModifyTooltips(Item item, List<TooltipLine> tooltips)
98+
{
99+
base.ModifyTooltips(item, tooltips);
100+
101+
IEnumerable<string> GetCombatInfo()
102+
{
103+
yield return Mod.GetTextValue("ItemOverhauls.Archery.ManualCharging");
104+
yield return Mod.GetTextValue("ItemOverhauls.Archery.HoverShots");
105+
}
106+
107+
TooltipUtils.ShowCombatInformation(Mod, tooltips, GetCombatInfo);
108+
}
109+
}

Common/Archery/ItemArrowRendering.cs

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using Microsoft.Xna.Framework;
3+
using Microsoft.Xna.Framework.Graphics;
4+
using Terraria;
5+
using Terraria.DataStructures;
6+
using Terraria.GameContent;
7+
using Terraria.ID;
8+
using Terraria.ModLoader;
9+
using TerrariaOverhaul.Common.Charging;
10+
using TerrariaOverhaul.Core.ItemComponents;
11+
using TerrariaOverhaul.Core.Time;
12+
13+
namespace TerrariaOverhaul.Common.Archery;
14+
15+
[Autoload(Side = ModSide.Client)]
16+
public sealed class ItemArrowRendering : ItemComponent
17+
{
18+
public bool Visible { get; private set; }
19+
public byte ArrowCount { get; private set; } = 1;
20+
public float AnimationProgress { get; private set; }
21+
public Projectile? AmmoProjectileSample { get; private set; }
22+
23+
public override void SetDefaults(Item item)
24+
{
25+
// Hardcoded for now.
26+
if (item.type == ItemID.Tsunami) {
27+
ArrowCount = 5;
28+
}
29+
}
30+
31+
public override void HoldItem(Item item, Player player)
32+
{
33+
Visible = Enabled && UpdateIsVisible(item, player);
34+
}
35+
36+
private bool UpdateIsVisible(Item item, Player player)
37+
{
38+
// Only render while a use is in progress
39+
if (!player.ItemAnimationActive) {
40+
return false;
41+
}
42+
43+
// Must be charging the weapon
44+
if (item.TryGetGlobalItem(out ItemPowerAttacks powerAttacks) && powerAttacks.IsCharging) {
45+
AnimationProgress = powerAttacks.Charge.Progress;
46+
} else if (item.TryGetGlobalItem(out ItemPrimaryUseCharging primaryCharging) && primaryCharging.Charge.Active) {
47+
AnimationProgress = primaryCharging.Charge.Progress;
48+
} else {
49+
return false;
50+
}
51+
52+
// Update ammo kind only at the beginning of the animation.
53+
if (!Visible) {
54+
player.PickAmmo(item, out int projectileType, out _, out _, out _, out _, dontConsume: true);
55+
56+
if (!ContentSamples.ProjectilesByType.TryGetValue(projectileType, out var projectile) || projectile == null) {
57+
return false;
58+
}
59+
60+
AmmoProjectileSample = projectile;
61+
}
62+
63+
return true;
64+
}
65+
}
66+
67+
[Autoload(Side = ModSide.Client)]
68+
public sealed class ArrowPlayerDrawLayer : PlayerDrawLayer
69+
{
70+
public override Position GetDefaultPosition()
71+
=> new AfterParent(PlayerDrawLayers.HeldItem);
72+
73+
public override bool GetDefaultVisibility(PlayerDrawSet drawInfo)
74+
{
75+
// Don't render as after-image
76+
return drawInfo.shadow == 0f;
77+
}
78+
79+
protected override void Draw(ref PlayerDrawSet drawInfo)
80+
{
81+
var player = drawInfo.drawPlayer;
82+
83+
// Must be holding a weapon with the ItemArrowRendering component.
84+
if (player.HeldItem is not { IsAir: false } weapon
85+
|| !weapon.TryGetGlobalItem(out ItemArrowRendering arrowRendering)
86+
|| arrowRendering is not { Enabled: true, Visible: true, AmmoProjectileSample: Projectile projectile }) {
87+
return;
88+
}
89+
90+
// Attack info
91+
float itemRotation = player.itemRotation + (player.direction < 0 ? MathHelper.Pi : 0f);
92+
Vector2 itemDirection = itemRotation.ToRotationVector2();
93+
94+
// Texture info
95+
Main.instance.LoadProjectile(projectile.type);
96+
97+
var projectileTexture = TextureAssets.Projectile[projectile.type].Value;
98+
var frame = new SpriteFrame(1, (byte)Main.projFrames[projectile.type]);
99+
var sourceRectangle = frame.GetSourceRectangle(projectileTexture);
100+
101+
// Animation
102+
const float StartOffset = 32f;
103+
const float EndOffset = 14f;
104+
105+
float animationProgress = arrowRendering.AnimationProgress;
106+
float positionOffsetEasing = 1f - MathF.Pow(1f - animationProgress, 2f);
107+
var positionOffset = itemDirection * MathHelper.Lerp(StartOffset, EndOffset, positionOffsetEasing);
108+
float rotationOffsetIntensity = Math.Min(1f, MathF.Pow(animationProgress, 5f) + 0.1f);
109+
float rotationOffset = MathF.Sin(TimeSystem.RenderTime * 50f) * MathHelper.ToRadians(7.5f) * rotationOffsetIntensity;
110+
111+
for (int i = 0; i < arrowRendering.ArrowCount; i++) {
112+
int arrowStep = (int)MathF.Ceiling(i * 0.5f) * (i % 2 == 0 ? 1 : -1);
113+
var itemDirectionRight = itemDirection.RotatedBy(MathHelper.PiOver2);
114+
var arrowOffset = (itemDirectionRight * arrowStep * 8f) + (itemDirection * MathF.Abs(arrowStep) * -2f);
115+
116+
// Drawing info
117+
var position = player.Center + positionOffset + arrowOffset;
118+
float rotation = itemRotation - MathHelper.PiOver2 * player.direction + rotationOffset;
119+
Vector2 origin = sourceRectangle.Size() * 0.5f;
120+
float scale = 1.0f;
121+
var effect = player.direction > 0 ? SpriteEffects.FlipVertically : SpriteEffects.None;
122+
var color = Lighting.GetColor(position.ToTileCoordinates());
123+
124+
// Drawing
125+
drawInfo.DrawDataCache.Add(new DrawData(projectileTexture, position - Main.screenPosition, sourceRectangle, color, rotation, origin, scale, effect, 0));
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)