2026-01-09 15:29:00 -05:00
|
|
|
package kamkeel.npcs.controllers.data.ability;
|
|
|
|
|
|
2026-01-31 21:55:39 -03:00
|
|
|
import kamkeel.npcs.controllers.data.ability.type.*;
|
2026-01-30 05:01:54 -05:00
|
|
|
import kamkeel.npcs.controllers.SyncController;
|
2026-02-14 21:06:12 -05:00
|
|
|
import net.minecraft.entity.EntityLivingBase;
|
2026-01-09 15:29:00 -05:00
|
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
|
|
|
import noppes.npcs.CustomNpcs;
|
|
|
|
|
import noppes.npcs.LogWriter;
|
|
|
|
|
import noppes.npcs.api.handler.IAbilityHandler;
|
|
|
|
|
import noppes.npcs.util.NBTJsonUtil;
|
|
|
|
|
|
2026-02-16 01:15:52 -05:00
|
|
|
import net.minecraft.entity.player.EntityPlayer;
|
|
|
|
|
|
2026-01-09 15:29:00 -05:00
|
|
|
import java.io.File;
|
2026-01-27 01:33:12 -03:00
|
|
|
import java.util.*;
|
2026-02-16 01:15:52 -05:00
|
|
|
import java.util.function.Predicate;
|
2026-01-09 15:29:00 -05:00
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
|
|
|
|
public class AbilityController implements IAbilityHandler {
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
public static AbilityController Instance = new AbilityController();
|
2026-01-09 15:29:00 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// ── Core Registries ──────────────────────────────────────────────────────
|
|
|
|
|
private final Map<String, Supplier<Ability>> abilityTypes = new LinkedHashMap<>();
|
|
|
|
|
private final Map<String, Ability> builtAbilities = new LinkedHashMap<>();
|
2026-01-30 05:01:54 -05:00
|
|
|
private final Map<String, Ability> customAbilities = new LinkedHashMap<>();
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// ── Extension Points ─────────────────────────────────────────────────────
|
2026-02-06 20:25:52 -05:00
|
|
|
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
|
2026-02-14 14:43:25 -05:00
|
|
|
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
|
2026-02-14 21:06:12 -05:00
|
|
|
private final List<IAbilityExtender> extenders = new ArrayList<>();
|
2026-02-16 01:15:52 -05:00
|
|
|
private final List<Predicate<EntityPlayer>> flightCheckers = new ArrayList<>();
|
2026-02-06 20:25:52 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// ── Derived State ────────────────────────────────────────────────────────
|
2026-02-12 20:30:12 -05:00
|
|
|
private final Set<String> builtInTypeIds = new HashSet<>();
|
|
|
|
|
|
2026-01-09 15:29:00 -05:00
|
|
|
public AbilityController() {
|
2026-01-30 05:01:54 -05:00
|
|
|
registerBuiltinTypes();
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-14 14:43:25 -05:00
|
|
|
// TYPE REGISTRATION
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public void registerType(String factoryKey, Supplier<Ability> factory) {
|
|
|
|
|
Ability temp = factory.get();
|
|
|
|
|
String typeId = temp.getTypeId();
|
|
|
|
|
if (abilityTypes.containsKey(typeId)) {
|
|
|
|
|
LogWriter.info("AbilityController: Overwriting type: " + typeId);
|
|
|
|
|
}
|
|
|
|
|
abilityTypes.put(typeId, factory);
|
|
|
|
|
if (temp.isBuiltIn()) {
|
|
|
|
|
builtInTypeIds.add(typeId);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
}
|
2026-01-09 15:29:00 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public Ability create(String typeId) {
|
|
|
|
|
Supplier<Ability> factory = abilityTypes.get(typeId);
|
|
|
|
|
return factory != null ? factory.get() : null;
|
|
|
|
|
}
|
2026-01-09 15:29:00 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public Ability fromNBT(NBTTagCompound nbt) {
|
|
|
|
|
if (nbt == null || !nbt.hasKey("typeId")) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-01-30 05:01:54 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
String typeId = nbt.getString("typeId");
|
|
|
|
|
Supplier<Ability> factory = abilityTypes.get(typeId);
|
|
|
|
|
if (factory == null) {
|
|
|
|
|
LogWriter.info("AbilityController: Unknown ability type: " + typeId);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ability ability = factory.get();
|
|
|
|
|
ability.readNBT(nbt);
|
|
|
|
|
return ability;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void registerBuiltinTypes() {
|
|
|
|
|
registerType("cnpc:slam", AbilitySlam::new);
|
|
|
|
|
registerType("cnpc:heavy_hit", AbilityHeavyHit::new);
|
|
|
|
|
registerType("cnpc:cutter", AbilityCutter::new);
|
|
|
|
|
|
|
|
|
|
registerType("cnpc:sweeper", AbilitySweeper::new);
|
|
|
|
|
registerType("cnpc:projectile", AbilityProjectile::new);
|
|
|
|
|
registerType("cnpc:orb", AbilityOrb::new);
|
|
|
|
|
registerType("cnpc:disc", AbilityDisc::new);
|
|
|
|
|
registerType("cnpc:laser_shot", AbilityLaserShot::new);
|
|
|
|
|
registerType("cnpc:beam", AbilityBeam::new);
|
|
|
|
|
|
|
|
|
|
registerType("cnpc:charge", AbilityCharge::new);
|
|
|
|
|
registerType("cnpc:dash", AbilityDash::new);
|
|
|
|
|
registerType("cnpc:teleport", AbilityTeleport::new);
|
|
|
|
|
registerType("cnpc:vortex", AbilityVortex::new);
|
|
|
|
|
registerType("cnpc:shockwave", AbilityShockwave::new);
|
|
|
|
|
|
|
|
|
|
registerType("cnpc:guard", AbilityGuard::new);
|
|
|
|
|
registerType("cnpc:heal", AbilityHeal::new);
|
|
|
|
|
|
|
|
|
|
registerType("cnpc:hazard", AbilityHazard::new);
|
|
|
|
|
registerType("cnpc:trap", AbilityTrap::new);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// BUILT-IN ABILITIES
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
public void registerAbility(String name, Ability ability) {
|
|
|
|
|
if (builtAbilities.containsKey(name)) {
|
|
|
|
|
LogWriter.info("AbilityController: Overwriting built-in ability: " + name);
|
|
|
|
|
}
|
|
|
|
|
if (!ability.isBuiltIn()) {
|
|
|
|
|
ability.setName(name);
|
|
|
|
|
}
|
|
|
|
|
builtAbilities.put(name, ability);
|
|
|
|
|
LogWriter.info("Registered ability: " + name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Ability getAbility(String name) {
|
|
|
|
|
return builtAbilities.get(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Ability getAbilityByDisplayName(String displayName) {
|
|
|
|
|
for (Ability ability : builtAbilities.values()) {
|
|
|
|
|
if (ability.getName().equals(displayName)) {
|
|
|
|
|
return ability;
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
return null;
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public Set<String> getAbilityNames() {
|
|
|
|
|
return new LinkedHashSet<>(builtAbilities.keySet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// CUSTOM ABILITIES
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
// Incremented on any custom ability change; used by AbilitySlot for cache invalidation
|
|
|
|
|
private int customAbilityRevision = 0;
|
|
|
|
|
|
|
|
|
|
public void load() {
|
|
|
|
|
customAbilities.clear();
|
|
|
|
|
|
|
|
|
|
File dir = getDir();
|
|
|
|
|
File[] files = dir.exists() ? dir.listFiles() : null;
|
|
|
|
|
if (files != null) {
|
|
|
|
|
for (File file : files) {
|
|
|
|
|
if (!file.isFile() || !file.getName().endsWith(".json")) continue;
|
|
|
|
|
try {
|
|
|
|
|
String filename = file.getName();
|
|
|
|
|
String uuid = filename.substring(0, filename.length() - 5);
|
|
|
|
|
|
|
|
|
|
NBTTagCompound nbt = NBTJsonUtil.LoadFile(file);
|
|
|
|
|
Ability ability = fromNBT(nbt);
|
|
|
|
|
if (ability != null) {
|
|
|
|
|
ability.setId(uuid);
|
|
|
|
|
customAbilities.put(uuid, ability);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
LogWriter.error("Error loading custom ability: " + file.getAbsolutePath(), e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
|
|
|
|
|
customAbilityRevision++;
|
|
|
|
|
LogWriter.info("Loaded " + customAbilities.size() + " custom abilities");
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
public boolean saveCustomAbility(Ability ability) {
|
|
|
|
|
if (ability == null) return false;
|
|
|
|
|
|
|
|
|
|
String uuid = ability.getId();
|
|
|
|
|
if (uuid == null || uuid.isEmpty()) {
|
|
|
|
|
uuid = UUID.randomUUID().toString();
|
|
|
|
|
ability.setId(uuid);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File dir = getDir();
|
2026-01-30 05:01:54 -05:00
|
|
|
File fileNew = new File(dir, uuid + ".json_new");
|
|
|
|
|
File fileCurrent = new File(dir, uuid + ".json");
|
2026-01-09 15:29:00 -05:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
NBTTagCompound nbt = ability.writeNBT();
|
|
|
|
|
NBTJsonUtil.SaveFile(fileNew, nbt);
|
|
|
|
|
|
|
|
|
|
if (fileCurrent.exists()) {
|
|
|
|
|
fileCurrent.delete();
|
|
|
|
|
}
|
|
|
|
|
fileNew.renameTo(fileCurrent);
|
|
|
|
|
if (fileNew.exists()) {
|
|
|
|
|
fileNew.delete();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
customAbilities.put(uuid, ability);
|
2026-02-14 14:43:25 -05:00
|
|
|
customAbilityRevision++;
|
2026-01-30 05:01:54 -05:00
|
|
|
LogWriter.info("Saved custom ability: " + ability.getName() + " [" + uuid + "]");
|
|
|
|
|
SyncController.syncAllCustomAbilities();
|
2026-01-09 15:29:00 -05:00
|
|
|
return true;
|
|
|
|
|
} catch (Exception e) {
|
2026-01-30 05:01:54 -05:00
|
|
|
LogWriter.error("Error saving custom ability: " + uuid, e);
|
2026-01-09 15:29:00 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
public boolean deleteCustomAbility(String uuid) {
|
|
|
|
|
if (uuid == null || uuid.isEmpty()) return false;
|
2026-01-09 15:29:00 -05:00
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
Ability removed = customAbilities.remove(uuid);
|
|
|
|
|
if (removed == null) return false;
|
2026-01-09 15:29:00 -05:00
|
|
|
|
|
|
|
|
File dir = getDir();
|
2026-01-30 05:01:54 -05:00
|
|
|
File file = new File(dir, uuid + ".json");
|
2026-01-09 15:29:00 -05:00
|
|
|
if (file.exists()) {
|
|
|
|
|
file.delete();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
customAbilityRevision++;
|
2026-01-30 05:01:54 -05:00
|
|
|
LogWriter.info("Deleted custom ability: " + uuid);
|
|
|
|
|
SyncController.syncAllCustomAbilities();
|
2026-01-09 15:29:00 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public Ability getCustomAbility(String uuid) {
|
|
|
|
|
return customAbilities.get(uuid);
|
|
|
|
|
}
|
2026-01-30 05:01:54 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public boolean hasCustomAbility(String uuid) {
|
|
|
|
|
return customAbilities.containsKey(uuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Set<String> getCustomAbilityIds() {
|
|
|
|
|
return new LinkedHashSet<>(customAbilities.keySet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getCustomAbilityName(String uuid) {
|
|
|
|
|
Ability a = customAbilities.get(uuid);
|
|
|
|
|
return a != null ? a.getName() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Map<String, Ability> getCustomAbilities() {
|
|
|
|
|
return customAbilities;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setCustomAbilities(Map<String, Ability> synced) {
|
|
|
|
|
customAbilities.clear();
|
|
|
|
|
customAbilities.putAll(synced);
|
|
|
|
|
customAbilityRevision++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getCustomAbilityRevision() {
|
|
|
|
|
return customAbilityRevision;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private File getDir() {
|
|
|
|
|
File dir = new File(CustomNpcs.getWorldSaveDirectory(), "abilities");
|
|
|
|
|
if (!dir.exists()) {
|
|
|
|
|
dir.mkdirs();
|
2026-02-12 20:30:12 -05:00
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
return dir;
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-14 14:43:25 -05:00
|
|
|
// RESOLUTION
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
public Ability resolveAbility(String key) {
|
|
|
|
|
if (key == null || key.isEmpty()) return null;
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// Built-in: exact name
|
|
|
|
|
Ability builtIn = builtAbilities.get(key);
|
|
|
|
|
if (builtIn != null) return builtIn.deepCopy();
|
2026-01-30 05:01:54 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// Built-in: case-insensitive name
|
|
|
|
|
for (Map.Entry<String, Ability> entry : builtAbilities.entrySet()) {
|
2026-02-05 22:49:40 -05:00
|
|
|
if (entry.getKey().equalsIgnoreCase(key)) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return entry.getValue().deepCopy();
|
2026-02-05 22:49:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// Built-in: registry key / ID
|
|
|
|
|
for (Ability ability : builtAbilities.values()) {
|
2026-02-05 22:49:40 -05:00
|
|
|
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return ability.deepCopy();
|
2026-02-05 22:49:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// Custom: exact UUID
|
2026-01-30 05:01:54 -05:00
|
|
|
Ability custom = customAbilities.get(key);
|
2026-02-14 14:43:25 -05:00
|
|
|
if (custom != null) return custom.deepCopy();
|
2026-01-30 05:01:54 -05:00
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
// Custom: case-insensitive name
|
2026-02-05 22:49:40 -05:00
|
|
|
for (Ability ability : customAbilities.values()) {
|
|
|
|
|
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return ability.deepCopy();
|
2026-02-05 22:49:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
return null;
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-05 22:49:40 -05:00
|
|
|
public Set<String> getAbilityKeys() {
|
|
|
|
|
Set<String> keys = new LinkedHashSet<>();
|
2026-02-14 14:43:25 -05:00
|
|
|
for (Ability ability : builtAbilities.values()) {
|
2026-02-05 22:49:40 -05:00
|
|
|
String id = ability.getId();
|
|
|
|
|
if (id != null && !id.isEmpty()) {
|
|
|
|
|
keys.add(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-15 19:21:30 -05:00
|
|
|
// Use ability names for custom abilities (instead of UUIDs) for readable tab completion
|
|
|
|
|
for (Ability ability : customAbilities.values()) {
|
|
|
|
|
String name = ability.getName();
|
|
|
|
|
if (name != null && !name.isEmpty()) {
|
|
|
|
|
keys.add(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-05 22:49:40 -05:00
|
|
|
return keys;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Set<String> getPlayerAbilityKeys() {
|
|
|
|
|
Set<String> keys = new LinkedHashSet<>();
|
2026-02-14 14:43:25 -05:00
|
|
|
for (Ability ability : builtAbilities.values()) {
|
2026-02-05 22:49:40 -05:00
|
|
|
if (ability.getAllowedBy().allowsPlayer()) {
|
|
|
|
|
String id = ability.getId();
|
|
|
|
|
if (id != null && !id.isEmpty()) {
|
|
|
|
|
keys.add(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-15 19:21:30 -05:00
|
|
|
// Use ability names for custom abilities (instead of UUIDs) for readable tab completion
|
|
|
|
|
for (Ability ability : customAbilities.values()) {
|
|
|
|
|
if (ability.getAllowedBy().allowsPlayer()) {
|
|
|
|
|
String name = ability.getName();
|
|
|
|
|
if (name != null && !name.isEmpty()) {
|
|
|
|
|
keys.add(name);
|
|
|
|
|
}
|
2026-02-05 22:49:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return keys;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
public boolean hasAbility(String key) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return builtAbilities.containsKey(key) || customAbilities.containsKey(key);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:39:14 -05:00
|
|
|
/**
|
|
|
|
|
* Check if a key can be resolved to a valid ability without creating a deep copy.
|
|
|
|
|
* Uses the same lookup chain as {@link #resolveAbility(String)}.
|
|
|
|
|
*/
|
|
|
|
|
public boolean canResolveAbility(String key) {
|
|
|
|
|
if (key == null || key.isEmpty()) return false;
|
|
|
|
|
|
|
|
|
|
// Built-in: exact name
|
|
|
|
|
if (builtAbilities.containsKey(key)) return true;
|
|
|
|
|
|
|
|
|
|
// Built-in: case-insensitive name
|
|
|
|
|
for (String name : builtAbilities.keySet()) {
|
|
|
|
|
if (name.equalsIgnoreCase(key)) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Built-in: registry key / ID
|
|
|
|
|
for (Ability ability : builtAbilities.values()) {
|
|
|
|
|
if (ability.getId() != null && ability.getId().equalsIgnoreCase(key)) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Custom: exact UUID
|
|
|
|
|
if (customAbilities.containsKey(key)) return true;
|
|
|
|
|
|
|
|
|
|
// Custom: case-insensitive name
|
|
|
|
|
for (Ability ability : customAbilities.values()) {
|
|
|
|
|
if (ability.getName() != null && ability.getName().equalsIgnoreCase(key)) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-14 14:43:25 -05:00
|
|
|
// EXTENSION POINTS
|
2026-01-30 05:01:54 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public void registerVariant(String typeId, AbilityVariant variant) {
|
|
|
|
|
externalVariants.computeIfAbsent(typeId, k -> new ArrayList<>()).add(variant);
|
2026-01-30 05:01:54 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public List<AbilityVariant> getVariantsForType(String typeId) {
|
|
|
|
|
List<AbilityVariant> result = new ArrayList<>();
|
|
|
|
|
Ability temp = create(typeId);
|
|
|
|
|
if (temp != null) {
|
|
|
|
|
result.addAll(temp.getVariants());
|
2026-02-12 20:30:12 -05:00
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
List<AbilityVariant> ext = externalVariants.get(typeId);
|
|
|
|
|
if (ext != null) {
|
2026-02-15 18:05:07 -05:00
|
|
|
// If no built-in variants exist but external ones do,
|
|
|
|
|
// inject a "Base" variant so the user always gets a choice
|
|
|
|
|
if (result.isEmpty()) {
|
|
|
|
|
result.add(new AbilityVariant("ability.variant.base", a -> {}));
|
|
|
|
|
}
|
2026-02-14 14:43:25 -05:00
|
|
|
result.addAll(ext);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2026-01-30 05:01:54 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public void registerFieldProvider(IAbilityFieldProvider provider) {
|
|
|
|
|
fieldProviders.add(provider);
|
2026-01-30 05:01:54 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:43:25 -05:00
|
|
|
public List<IAbilityFieldProvider> getFieldProviders() {
|
|
|
|
|
return fieldProviders;
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:06:12 -05:00
|
|
|
public void registerExtender(IAbilityExtender extender) {
|
|
|
|
|
extenders.add(extender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<IAbilityExtender> getExtenders() {
|
|
|
|
|
return extenders;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 01:15:52 -05:00
|
|
|
public void registerFlightChecker(Predicate<EntityPlayer> checker) {
|
|
|
|
|
flightCheckers.add(checker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isPlayerFlying(EntityPlayer player) {
|
|
|
|
|
if (player.capabilities.isFlying) return true;
|
|
|
|
|
for (Predicate<EntityPlayer> checker : flightCheckers) {
|
|
|
|
|
if (checker.test(player)) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 21:06:12 -05:00
|
|
|
/**
|
|
|
|
|
* Fire onAbilityStart on all extenders. Returns false if ANY extender cancels.
|
|
|
|
|
*/
|
|
|
|
|
public boolean fireOnAbilityStart(Ability ability, EntityLivingBase caster, EntityLivingBase target) {
|
|
|
|
|
for (IAbilityExtender ext : extenders) {
|
|
|
|
|
if (!ext.onAbilityStart(ability, caster, target)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fire onAbilityTick on all extenders. Returns false if ANY extender says to interrupt.
|
|
|
|
|
*/
|
|
|
|
|
public boolean fireOnAbilityTick(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
|
|
|
|
AbilityPhase phase, int tick) {
|
|
|
|
|
for (IAbilityExtender ext : extenders) {
|
|
|
|
|
if (!ext.onAbilityTick(ability, caster, target, phase, tick)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fire onAbilityComplete on all extenders.
|
|
|
|
|
*/
|
|
|
|
|
public void fireOnAbilityComplete(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
|
|
|
|
boolean interrupted) {
|
|
|
|
|
for (IAbilityExtender ext : extenders) {
|
|
|
|
|
ext.onAbilityComplete(ability, caster, target, interrupted);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fire onAbilityDamage on all extenders. Chain of responsibility — first true wins.
|
|
|
|
|
*/
|
|
|
|
|
public boolean fireOnAbilityDamage(Ability ability, EntityLivingBase caster, EntityLivingBase target,
|
|
|
|
|
float damage, float knockback, float knockbackUp,
|
|
|
|
|
double knockbackDirX, double knockbackDirZ) {
|
|
|
|
|
for (IAbilityExtender ext : extenders) {
|
|
|
|
|
if (ext.onAbilityDamage(ability, caster, target, damage, knockback, knockbackUp,
|
|
|
|
|
knockbackDirX, knockbackDirZ)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 15:29:00 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-14 14:43:25 -05:00
|
|
|
// IAbilityHandler
|
2026-01-09 15:29:00 -05:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String[] getTypes() {
|
2026-02-14 14:43:25 -05:00
|
|
|
return abilityTypes.keySet().toArray(new String[0]);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 20:30:12 -05:00
|
|
|
public boolean isBuiltInType(String typeId) {
|
|
|
|
|
return builtInTypeIds.contains(typeId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 15:29:00 -05:00
|
|
|
@Override
|
|
|
|
|
public boolean hasType(String typeId) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return abilityTypes.containsKey(typeId);
|
2026-01-30 05:01:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String[] getAbilityNameArray() {
|
2026-02-14 14:43:25 -05:00
|
|
|
return builtAbilities.keySet().toArray(new String[0]);
|
2026-01-30 05:01:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean hasAbilityName(String name) {
|
2026-02-14 14:43:25 -05:00
|
|
|
return builtAbilities.containsKey(name);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2026-01-30 05:01:54 -05:00
|
|
|
public String[] getCustomAbilityIdArray() {
|
|
|
|
|
return customAbilities.keySet().toArray(new String[0]);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2026-01-30 05:01:54 -05:00
|
|
|
public boolean hasCustomAbilityId(String uuid) {
|
|
|
|
|
return customAbilities.containsKey(uuid);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2026-01-30 05:01:54 -05:00
|
|
|
public boolean deleteCustomAbilityById(String uuid) {
|
|
|
|
|
return deleteCustomAbility(uuid);
|
2026-01-09 15:29:00 -05:00
|
|
|
}
|
|
|
|
|
}
|