Skip to content

Commit a8e74d1

Browse files
feat(ravengard): starter abilities work off the drop and swap keys
Ability one fires on the drop key and ability two on swap offhand, the binds the captured lore names, with per ability cooldowns from the tooltips. Recovery and Med Kit heal instantly and Cool Off and Heal Wounds over ten seconds on the hud's health scale, the timed buffs sit in a registry for the damage code to read, and Shadows is real invisibility with the movement slow, broken by attacking or being attacked.
1 parent 724db39 commit a8e74d1

2 files changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package net.swofty.type.ravengardgeneric.classes;
2+
3+
import net.minestom.server.MinecraftServer;
4+
import net.minestom.server.entity.attribute.Attribute;
5+
import net.minestom.server.timer.TaskSchedule;
6+
import net.swofty.type.ravengardgeneric.user.RavengardPlayer;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.UUID;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
13+
/**
14+
* Runs the starter abilities. Heals work on the hud's 160 point scale over the forty point
15+
* vanilla pool, timed buffs sit in a registry the damage code reads, and Shadows is real
16+
* invisibility with the slow, cancelled by attacking or being hit.
17+
*/
18+
public final class RavengardAbilityService {
19+
/** Display health points per vanilla half heart pool: 160 shown over 40 vanilla. */
20+
private static final float DISPLAY_SCALE = 4f;
21+
22+
private static final Map<UUID, Map<RavengardAbility, Long>> COOLDOWNS = new ConcurrentHashMap<>();
23+
private static final Map<UUID, Map<RavengardAbility, Long>> BUFFS = new ConcurrentHashMap<>();
24+
25+
private RavengardAbilityService() {
26+
}
27+
28+
public static void use(RavengardPlayer player, int slot) {
29+
RavengardClass playerClass = player.getRavengardClass();
30+
if (playerClass == null || player.isTutorial()) {
31+
return;
32+
}
33+
List<RavengardAbility> abilities = playerClass.defaultAbilities();
34+
if (slot >= abilities.size()) {
35+
return;
36+
}
37+
RavengardAbility ability = abilities.get(slot);
38+
39+
long now = System.currentTimeMillis();
40+
long readyAt = COOLDOWNS.computeIfAbsent(player.getUuid(), ignored -> new ConcurrentHashMap<>())
41+
.getOrDefault(ability, 0L);
42+
if (readyAt > now) {
43+
player.sendMessage("§cThis ability is on cooldown for " + ((readyAt - now) / 1000 + 1) + "s!");
44+
return;
45+
}
46+
COOLDOWNS.get(player.getUuid()).put(ability, now + ability.getCooldownSeconds() * 1000L);
47+
48+
apply(player, ability);
49+
player.sendMessage("§aYou used §f" + ability.getDisplayName() + "§a!");
50+
}
51+
52+
private static void apply(RavengardPlayer player, RavengardAbility ability) {
53+
switch (ability) {
54+
case RECOVERY, MED_KIT -> heal(player, 30);
55+
case COOL_OFF, HEAL_WOUNDS -> healOverTime(player, 35, 10);
56+
case SHADOWS -> shadows(player);
57+
default -> buff(player, ability, 10);
58+
}
59+
}
60+
61+
/** Whether a timed buff from an ability is currently active, for the damage code to read. */
62+
public static boolean hasBuff(RavengardPlayer player, RavengardAbility ability) {
63+
Map<RavengardAbility, Long> buffs = BUFFS.get(player.getUuid());
64+
return buffs != null && buffs.getOrDefault(ability, 0L) > System.currentTimeMillis();
65+
}
66+
67+
private static void buff(RavengardPlayer player, RavengardAbility ability, int seconds) {
68+
BUFFS.computeIfAbsent(player.getUuid(), ignored -> new ConcurrentHashMap<>())
69+
.put(ability, System.currentTimeMillis() + seconds * 1000L);
70+
}
71+
72+
private static void heal(RavengardPlayer player, int displayAmount) {
73+
float max = (float) player.getAttributeValue(Attribute.MAX_HEALTH);
74+
player.setHealth(Math.min(max, player.getHealth() + displayAmount / DISPLAY_SCALE));
75+
}
76+
77+
private static void healOverTime(RavengardPlayer player, int displayTotal, int seconds) {
78+
float perSecond = displayTotal / (float) seconds / DISPLAY_SCALE;
79+
for (int second = 1; second <= seconds; second++) {
80+
MinecraftServer.getSchedulerManager().buildTask(() -> {
81+
if (player.isOnline()) {
82+
float max = (float) player.getAttributeValue(Attribute.MAX_HEALTH);
83+
player.setHealth(Math.min(max, player.getHealth() + perSecond));
84+
}
85+
}).delay(TaskSchedule.tick(second * 20)).schedule();
86+
}
87+
}
88+
89+
private static void shadows(RavengardPlayer player) {
90+
buff(player, RavengardAbility.SHADOWS, 25);
91+
player.setInvisible(true);
92+
var speed = player.getAttribute(Attribute.MOVEMENT_SPEED);
93+
double base = speed.getBaseValue();
94+
speed.setBaseValue(base * 0.5);
95+
MinecraftServer.getSchedulerManager().buildTask(() -> endShadows(player, base))
96+
.delay(TaskSchedule.tick(25 * 20)).schedule();
97+
}
98+
99+
public static void breakShadows(RavengardPlayer player) {
100+
if (!hasBuff(player, RavengardAbility.SHADOWS)) {
101+
return;
102+
}
103+
BUFFS.get(player.getUuid()).remove(RavengardAbility.SHADOWS);
104+
endShadows(player, 0.1);
105+
}
106+
107+
private static void endShadows(RavengardPlayer player, double baseSpeed) {
108+
if (!player.isOnline()) {
109+
return;
110+
}
111+
player.setInvisible(false);
112+
player.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(baseSpeed);
113+
}
114+
115+
public static void forget(UUID uuid) {
116+
COOLDOWNS.remove(uuid);
117+
BUFFS.remove(uuid);
118+
}
119+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package net.swofty.type.ravengardgeneric.event.actions.player;
2+
3+
import net.minestom.server.event.entity.EntityAttackEvent;
4+
import net.minestom.server.event.item.ItemDropEvent;
5+
import net.minestom.server.event.player.PlayerDisconnectEvent;
6+
import net.minestom.server.event.player.PlayerSwapItemEvent;
7+
import net.swofty.type.generic.event.EventNodes;
8+
import net.swofty.type.generic.event.HypixelEventClass;
9+
import net.swofty.type.generic.event.phase.EventPhase;
10+
import net.swofty.type.generic.event.phase.PhasedEvent;
11+
import net.swofty.type.ravengardgeneric.classes.RavengardAbilityService;
12+
import net.swofty.type.ravengardgeneric.user.RavengardPlayer;
13+
14+
/** The drop key fires ability one and swap offhand ability two, as the captured lore binds them. */
15+
public class ActionPlayerAbilities implements HypixelEventClass {
16+
17+
@PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = true, phase = EventPhase.GAMEPLAY)
18+
public void onDrop(ItemDropEvent event) {
19+
if (event.getPlayer() instanceof RavengardPlayer player) {
20+
event.setCancelled(true);
21+
RavengardAbilityService.use(player, 0);
22+
}
23+
}
24+
25+
@PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = true, phase = EventPhase.GAMEPLAY)
26+
public void onSwap(PlayerSwapItemEvent event) {
27+
if (event.getPlayer() instanceof RavengardPlayer player) {
28+
event.setCancelled(true);
29+
RavengardAbilityService.use(player, 1);
30+
}
31+
}
32+
33+
@PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = false, phase = EventPhase.GAMEPLAY)
34+
public void onAttack(EntityAttackEvent event) {
35+
if (event.getEntity() instanceof RavengardPlayer player) {
36+
RavengardAbilityService.breakShadows(player);
37+
}
38+
if (event.getTarget() instanceof RavengardPlayer target) {
39+
RavengardAbilityService.breakShadows(target);
40+
}
41+
}
42+
43+
@PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = false, phase = EventPhase.DISCONNECT)
44+
public void onDisconnect(PlayerDisconnectEvent event) {
45+
RavengardAbilityService.forget(event.getPlayer().getUuid());
46+
}
47+
}

0 commit comments

Comments
 (0)