Skip to content

Commit 85e5670

Browse files
authored
Merge pull request RobertSkalko#236 from AltimorTASDK/rework-spell-inputs
Improve spell input handling
2 parents 4fd5da7 + 795c7a9 commit 85e5670

5 files changed

Lines changed: 194 additions & 108 deletions

File tree

src/main/java/com/robertx22/mine_and_slash/database/data/game_balance_config/GameBalanceConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public void editForCompat() {
7272

7373
public double MIN_SPELL_COOLDOWN_MULTI = 0.2;
7474

75+
public int GLOBAL_COOLDOWN_TICKS = 3;
76+
7577
public double CRAFTED_GEAR_POTENTIAL_MULTI = 0.5;
7678

7779
public int MAX_BONUS_SPELL_LEVELS = 5;

src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/SpellConfiguration.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,7 @@ public SpellConfiguration setChargesAndRegen(String name, int charges, int ticks
8282
this.charge_regen = ticksToRegen;
8383
this.charges = charges;
8484
this.charge_name = name;
85-
86-
87-
if (ticksToRegen > (20 * 30)) {
88-
this.cooldown_ticks = 0; // we force the cooldown to be the same for all spells with charges so it feels consistent and good
89-
} else {
90-
this.cooldown_ticks = 0; // we force the cooldown to be the same for all spells with charges so it feels consistent and good
91-
}
92-
93-
85+
this.cooldown_ticks = 3; // we force the cooldown to be the same for all spells with charges so it feels consistent and good
9486
return this;
9587
}
9688

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.robertx22.mine_and_slash.event_hooks.player;
22

3+
import java.util.Stack;
4+
35
import com.robertx22.library_of_exile.main.Packets;
46
import com.robertx22.mine_and_slash.config.forge.ClientConfigs;
57
import com.robertx22.mine_and_slash.gui.screens.character_screen.MainHubScreen;
@@ -16,13 +18,15 @@ public class OnKeyPress {
1618

1719
public static int cooldown = 0;
1820

21+
// Store what order spell keys are pressed in to prioritize most recently pressed
22+
private static Stack<SpellKeybind> spellKeysPressed = new Stack<>();
1923

20-
public static void onEndTick(Minecraft mc) {
24+
// Number of last spell sent to the server
25+
private static int lastSpellNumber = -1;
26+
// Timer to resend packet so the server knows we want to keep casting
27+
private static int spellPacketResendTimer = 0;
2128

22-
if (cooldown > 0) {
23-
cooldown--;
24-
return;
25-
}
29+
public static void onEndTick(Minecraft mc) {
2630

2731
if (mc.player == null) {
2832
return;
@@ -32,6 +36,12 @@ public static void onEndTick(Minecraft mc) {
3236
return;
3337
}
3438

39+
updateSpellInputs();
40+
41+
if (cooldown > 0) {
42+
cooldown--;
43+
return;
44+
}
3545

3646
if (KeybindsRegister.UNSUMMON.isDown()) {
3747
Packets.sendToServer(new UnsummonPacket());
@@ -44,46 +54,69 @@ public static void onEndTick(Minecraft mc) {
4454
cooldown = 5;
4555
} else if (KeybindsRegister.QUICK_DRINK_POTION.consumeClick()) {
4656
Packets.sendToServer(new QuickUsePotionPacket());
47-
} else {
57+
}
58+
}
4859

49-
int number = -1;
60+
private static boolean checkToAddSpellKeyPress(SpellKeybind key) {
61+
if (key.key.consumeClick()) {
62+
spellKeysPressed.add(key);
63+
// Consume any remaining clicks
64+
while (key.key.consumeClick()) {
65+
}
66+
return true;
67+
}
68+
return false;
69+
}
5070

51-
var keys = SpellKeybind.ALL;
71+
private static void updateSpellInputs() {
72+
var keys = SpellKeybind.ALL;
5273

53-
if (ClientConfigs.getConfig().HOTBAR_SWAPPING.get()) {
54-
keys = SpellKeybind.FIRST_HOTBAR_KEYS;
55-
}
74+
if (ClientConfigs.getConfig().HOTBAR_SWAPPING.get()) {
75+
keys = SpellKeybind.FIRST_HOTBAR_KEYS;
76+
}
77+
78+
spellKeysPressed.removeIf(key -> !key.key.isDown());
5679

57-
for (SpellKeybind key : keys) {
58-
if (key.key.isDown()) {
59-
number = key.getIndex();
80+
// Prioritize binds with modifiers in case the same key is reused but with a modifier
81+
for (SpellKeybind key : keys) {
82+
if (key.key.getKeyModifier() == KeyModifier.NONE) {
83+
if (checkToAddSpellKeyPress(key)) {
84+
break;
6085
}
6186
}
62-
// we always use the key modifier when both are pressed but use same keybind
63-
for (SpellKeybind key : keys) {
64-
if (key.key.getKeyModifier() != KeyModifier.NONE && key.key.isDown()) {
65-
number = key.getIndex();
87+
}
88+
89+
for (SpellKeybind key : keys) {
90+
if (key.key.getKeyModifier() != KeyModifier.NONE) {
91+
if (checkToAddSpellKeyPress(key)) {
92+
break;
6693
}
6794
}
95+
}
6896

69-
// try see if consuming clicks fixes the random key bugs
70-
for (SpellKeybind key : SpellKeybind.ALL) {
71-
key.key.consumeClick();
72-
}
97+
int number;
7398

74-
if (ClientConfigs.getConfig().HOTBAR_SWAPPING.get()) {
75-
if (SpellKeybind.IS_ON_SECONd_HOTBAR) {
76-
if (number > -1) {
77-
number += 4;
78-
}
79-
}
99+
if (!spellKeysPressed.empty()) {
100+
number = spellKeysPressed.lastElement().getIndex();
101+
if (SpellKeybind.IS_ON_SECONd_HOTBAR) {
102+
number += 4;
80103
}
104+
} else {
105+
number = -1;
106+
}
81107

82-
if (number > -1) {
83-
// todo make sure its not lagging servers
84-
Packets.sendToServer(new TellServerToCastSpellPacket(number));
85-
cooldown = 2;
108+
if (number == lastSpellNumber) {
109+
if (number == -1) {
110+
return;
111+
}
112+
if (spellPacketResendTimer > 0) {
113+
spellPacketResendTimer--;
114+
return;
86115
}
87116
}
117+
118+
Packets.sendToServer(new TellServerToCastSpellPacket(number));
119+
lastSpellNumber = number;
120+
spellPacketResendTimer = 2;
88121
}
89122
}

src/main/java/com/robertx22/mine_and_slash/saveclasses/spells/SpellCastingData.java

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.robertx22.library_of_exile.util.ExplainedResult;
55
import com.robertx22.mine_and_slash.a_libraries.player_animations.PlayerAnimations;
66
import com.robertx22.mine_and_slash.capability.entity.EntityData;
7+
import com.robertx22.mine_and_slash.capability.player.data.PlayerConfigData;
78
import com.robertx22.mine_and_slash.config.forge.compat.CompatConfig;
89
import com.robertx22.mine_and_slash.database.data.exile_effects.ExileEffect;
910
import com.robertx22.mine_and_slash.database.data.exile_effects.ExileEffectInstanceData;
@@ -32,9 +33,12 @@
3233
import net.minecraft.server.level.ServerPlayer;
3334
import net.minecraft.world.entity.LivingEntity;
3435
import net.minecraft.world.entity.player.Player;
36+
import net.minecraft.world.item.ItemStack;
3537

3638
import java.util.ArrayList;
3739
import java.util.HashMap;
40+
import java.util.Iterator;
41+
import java.util.LinkedList;
3842
import java.util.List;
3943
import java.util.Map;
4044

@@ -207,13 +211,93 @@ public SkillGemData getData() {
207211
}
208212
}
209213

214+
private static class SpellInputBufferEntry {
215+
public int number;
216+
public int ticksLeft;
217+
218+
public SpellInputBufferEntry(int number) {
219+
this.number = number;
220+
this.ticksLeft = 5;
221+
}
222+
}
223+
210224
public int castTickLeft = 0;
211225
public int castTicksDone = 0;
212226
public int spellTotalCastTicks = 0;
213227
public CalculatedSpellData calcSpell = null;
214228
public Boolean casting = false;
215229
public ChargeData charges = new ChargeData();
216230

231+
// Spell inputs to continuously attempt
232+
transient List<SpellInputBufferEntry> spellInputBuffer = new LinkedList<>();
233+
// The hotbar index of the spell key the client is holding
234+
transient int spellInputNumber = -1;
235+
// How many ticks left without another packet before we stop casting
236+
transient int spellInputTimeoutTicks = 0;
237+
238+
public void onSpellInputPressed(int number) {
239+
if (number != -1 && number != spellInputNumber) {
240+
// Cap size to prevent DoS
241+
if (spellInputBuffer.size() < 10) {
242+
spellInputBuffer.add(new SpellInputBufferEntry(number));
243+
}
244+
}
245+
spellInputNumber = number;
246+
spellInputTimeoutTicks = 8;
247+
}
248+
249+
public boolean tryStartSpellCast(Player player, Spell spell) {
250+
251+
var data = Load.player(player);
252+
var cds = Load.Unit(player).getCooldowns();
253+
254+
if (player.isBlocking() || player.swinging) {
255+
return false;
256+
}
257+
258+
if (cds.isOnCooldown("global_cooldown")) {
259+
return false;
260+
}
261+
262+
if (spell != null) {
263+
264+
var can = canCast(spell, player);
265+
266+
if (can.can) {
267+
268+
ItemStack wep = player.getMainHandItem();
269+
270+
if (!wep.isEmpty() && !RepairUtils.isItemBroken(wep)) {
271+
wep.hurt(1, player.getRandom(), (ServerPlayer) player);
272+
}
273+
274+
SpellCastContext c = new SpellCastContext(player, 0, spell);
275+
setToCast(c);
276+
spell.spendResources(c);
277+
278+
// Limit global cooldown to spell cooldown to allow rapid fire spells
279+
int gcd = Math.min(GameBalanceConfig.get().GLOBAL_COOLDOWN_TICKS, spell.getCooldownTicks(c));
280+
cds.setOnCooldown("global_cooldown", gcd);
281+
282+
data.playerDataSync.setDirty();
283+
return true;
284+
} else if (!cds.isOnCooldown("spell_fail")) {
285+
cds.setOnCooldown("spell_fail", 40);
286+
if (can.answer != null) {
287+
if (Load.Unit(player).getLevel() < 15 || Load.player(player).config.isConfigEnabled(PlayerConfigData.Config.CAST_FAIL)) {
288+
player.sendSystemMessage(Chats.CAST_FAILED.locName().append(can.answer));
289+
}
290+
}
291+
}
292+
293+
}
294+
return false;
295+
}
296+
297+
public boolean tryStartSpellCast(Player player, int number) {
298+
Spell spell = Load.player(player).getSkillGemInventory().getHotbarGem(number).getSpell();
299+
return tryStartSpellCast(player, spell);
300+
}
217301

218302
public void cancelCast(LivingEntity entity) {
219303
try {
@@ -252,11 +336,44 @@ public boolean isCasting() {
252336

253337
transient static Spell lastSpell = null;
254338

339+
private void processSpellInputs(Player player) {
340+
341+
if (spellInputTimeoutTicks > 0) {
342+
spellInputTimeoutTicks--;
343+
} else {
344+
// client stopped responding, don't cast forever
345+
spellInputNumber = -1;
346+
}
347+
348+
// Prune input buffer
349+
for (Iterator<SpellInputBufferEntry> iterator = spellInputBuffer.iterator(); iterator.hasNext(); ) {
350+
if (iterator.next().ticksLeft-- == 0) {
351+
iterator.remove();
352+
}
353+
}
354+
355+
// See if any buffered inputs succeed
356+
for (Iterator<SpellInputBufferEntry> iterator = spellInputBuffer.iterator(); iterator.hasNext(); ) {
357+
if (tryStartSpellCast(player, iterator.next().number)) {
358+
iterator.remove();
359+
return;
360+
}
361+
}
362+
363+
// If not, try held input
364+
if (spellInputNumber != -1) {
365+
tryStartSpellCast(player, spellInputNumber);
366+
}
367+
}
368+
255369
public void onTimePass(LivingEntity entity) {
256370

257-
try {
371+
if (entity instanceof ServerPlayer player) {
372+
processSpellInputs(player);
373+
}
258374

259-
if (isCasting()) {
375+
if (isCasting()) {
376+
try {
260377
Spell spell = this.calcSpell.getSpell();
261378

262379
SpellCastContext ctx = new SpellCastContext(entity, castTicksDone, spell);
@@ -291,15 +408,13 @@ public void onTimePass(LivingEntity entity) {
291408

292409
this.calcSpell = null;
293410
}
294-
} else {
295-
296-
lastSpell = null;
411+
} catch (Exception e) {
412+
e.printStackTrace();
413+
this.cancelCast(entity);
414+
// cancel when error, cus this is called on tick, so it doesn't crash servers when 1 spell fails
297415
}
298-
299-
} catch (Exception e) {
300-
e.printStackTrace();
301-
this.cancelCast(entity);
302-
// cancel when error, cus this is called on tick, so it doesn't crash servers when 1 spell fails
416+
} else {
417+
lastSpell = null;
303418
}
304419
}
305420

0 commit comments

Comments
 (0)