/** * SagaLoreStats Public API Interface * Provides unified API interface for other plugins to call * * Usage Examples: * ```java * // Get player attribute value * double attack = SagaLoreStatsAPI.getPlayerAttributeValue(player, "attack"); * * // Add temporary attributes * List<String> attributes = Arrays.asList("Attack: +50", "Health: +100"); * SagaLoreStatsAPI.addTemporaryAttributes(player, "Temporary BUFF", attributes); * * // Remove temporary attributes * SagaLoreStatsAPI.removeTemporaryAttributes(player, "Temporary BUFF"); * ``` */ publicclass SagaLoreStatsAPI
{
privatestatic SagaLoreStats plugin
;
/** * Initialize API (called internally by plugin) * @param plugin Plugin instance */ publicstaticvoid init
(SagaLoreStats plugin
);
/** * Get plugin instance * @return Plugin instance, returns null if plugin is not enabled */ publicstatic SagaLoreStats getPlugin
();
/** * Check if plugin is available * @return Whether plugin is available */ publicstaticboolean isAvailable
();
// ==================== Player Attribute API ====================
/** * Get player attribute value (includes equipment attributes and script API attributes) * @param player Player * @param attributeKey Attribute key (e.g.: "attack", "defense", "health", etc.) * @return Attribute value, returns 0.0 if attribute doesn't exist */ publicstaticdouble getPlayerAttributeValue
(Player player,
String attributeKey
);
/** * Get all player attributes (includes equipment attributes and script API attributes) * @param player Player * @return Attribute mapping, key is attribute key, value is attribute value */ publicstatic Map
<String, Double
> getPlayerAttributes
(Player player
);
/** * Get player combat power * @param player Player * @return Combat power value */ publicstaticdouble getPlayerCombatPower
(Player player
);
// ==================== Entity Attribute API ====================
/** * Get entity attribute value (supports players and monsters) * @param entity Entity * @param attributeKey Attribute key * @return Attribute value, returns 0.0 if attribute doesn't exist */ publicstaticdouble getEntityAttributeValue
(LivingEntity entity,
String attributeKey
);
/** * Get all entity attributes (supports players and monsters) * @param entity Entity * @return Attribute mapping, key is attribute key, value is attribute value */ publicstatic Map
<String, Double
> getEntityAttributes
(LivingEntity entity
);
// ==================== Temporary Attribute API ====================
/** * Add temporary attribute source to entity * @param entity Entity * @param sourceName Attribute source name (used for identification and removal) * @param attributes Attribute list, format like ["Attack: +50", "Health: +100"] */ publicstaticvoid addTemporaryAttributes
(LivingEntity entity,
String sourceName, List
<String
> attributes
);
/** * Get all temporary attribute sources of entity * @param entity Entity * @return Attribute source mapping, key is source name, value is attribute list */ publicstatic Map
<String, List
<String
>> getTemporaryAttributeSources
(LivingEntity entity
);
/** * Clear all temporary attribute sources of entity * @param entity Entity */ publicstaticvoid clearTemporaryAttributes
(LivingEntity entity
);
// ==================== Attribute Registration API ====================
// ==================== Attribute Data API ====================
/** * Get attribute data * @param attributeKey Attribute key * @return Attribute data, returns null if doesn't exist */ publicstatic AttributeData getAttributeData
(String attributeKey
);
/** * Get all attribute data * @return Attribute data mapping, key is attribute key, value is attribute data */ publicstatic Map
<String, AttributeData
> getAllAttributeData
();
/** * Get attribute data by specified type * @param type Attribute type * @return Attribute data list */ publicstatic List
<AttributeData
> getAttributeDataByType
(AttributeType type
);
// ==================== Cache Management API ====================
/** * Clear player attribute cache * @param player Player */ publicstaticvoid clearPlayerCache
(Player player
);
/** * Force refresh player attribute cache * @param player Player */ publicstaticvoid refreshPlayerAttributes
(Player player
);
/** * Force refresh all online players' attribute cache */ publicstaticvoid refreshAllPlayersAttributes
();
// ==================== Utility Method API ====================
/** * Calculate attribute effect * @param attributeKey Attribute key * @param attacker Attacker * @param entity Defender * @param attackerDamage Damage dealt by attacker * @param entityDamage Damage dealt by defender * @param value Attribute value * @return Calculation result */ publicstaticdouble calculateAttributeEffect
(String attributeKey, LivingEntity attacker,
LivingEntity entity,
double attackerDamage,
double entityDamage,
double value
);
/** * Update player attributes * @param player Player */ publicstaticvoid updatePlayerAttributes
(Player player
); }
Introduction
SagaLoreStats is a powerful custom attribute plugin for Minecraft servers, driven by item Lore recognition. It allows server administrators to create a deep and engaging item system by granting players various attribute bonuses (like Attack Power, Defense, Health, etc.) based on specific text found in an item's Lore.
Core Features
Lore-Driven Attribute System: Attributes are dynamically applied by recognizing specific text patterns in item Lores.
Diverse Attribute Types: Supports Attack/Defense, Other, Update (time-based), and Runtime (script-based) attributes.
Customizable Attribute Formulas: Define your own calculation formulas for attributes, offering fine-grained control over game balance.
Combat Power System: Calculates a player's overall combat strength based on their equipped attributes.
Equipment Restriction System: Set limits on gear usage based on level, class, item type, and more.
Attribute GUI Panel: An intuitive interface (`/sls stats`) for players to view their current attributes.
Powerful Scripting System: Extend and create custom attribute functionalities using JavaScript.
PlaceholderAPI Support: Provides a rich set of placeholders for integration with other plugins.
Advanced Combat Attributes: Includes Armor Penetration, Armor Strength, Armor Break, Critical Evasion, Crit Damage Resistance, and more.
Vitality Attribute System: Manage health bonuses, health regeneration per second, and other life-related stats.
MythicMobs Compatibility: Seamlessly integrates with MythicMobs.
Attribute System Explained
Attribute Types & Priority SagaLoreStats categorizes attributes to manage their application and calculation order. Higher priority numbers are processed first.
Examples: Health (health), Movement Speed Bonus (moving), Percentage Health Regen (health_regen), Health Regen Per Second (health_regen_second), Dynamic Attribute Test (dynamic_attr_test).
Functionality: These attributes are updated via a scheduled task (default: every 5 seconds).
Default Priorities: Dynamic Attribute Test (15) > Health (10) > Movement Speed (5) > Health Regen Per Second (2) > Percentage Regen (1).
Runtime Attributes (RUNTIME)
Functionality: Custom, periodically triggered attributes primarily implemented and extended via the Scripting System.
Attribute Recognition & Loading
Lore Parsing:
The `LoreParser` uses regular expressions to extract attribute text from item Lores.
Supports various formats like `Attribute Name: +Value` or `+Value Attribute Name`.
Formats are customizable in `format.yml`.
Attribute Update Triggers:
When a player joins the server (configurable).
When a player equips or switches items (configurable).
Periodically via a scheduled task (default: every 5 seconds).
Probability Attribute Trigger Mechanism:
Chance-based attributes (Dodge, Crit, Vampire, etc.) use a random number generator to determine if they activate.
The `FormulaExecutor.isProbabilityAttribute` method defines which attributes are probabilistic.
Calculations are performed by `ExpressionEvaluator`.
Supports math functions like `min`, `max`.
Example Formulas:
Critical Hit Damage: `{attackerAttack}*({entityA:crit_damage}-{entityB:crit_damage_resist})/100` (Note: This is likely intended to be based on the base damage, not attacker's total attack. A more common crit damage formula would be `({damage} * ({entityA:crit_damage}/100)) - ({damage} * ({entityB:crit_damage_resist}/100))`, or simpler if crit_damage is a multiplier.) The original was `{attackerAttack}*({entityA:暴击倍率}-{entityB:暴击抵抗})/100`
Lifesteal: `{damage}*{value}/100`
Armor Penetration: `min({value}, {entityB:defense})` (Reduces target's defense by `value`, capped at target's defense)
Armor Strength (Damage Reduction): `{damage}*{value}/100` (Reduces incoming damage by a percentage based on `value`)
Armor Break: `min({value}, {entityB:armor_strength})` (Reduces target's armor strength by `value`, capped at target's armor strength)
Critical Evasion: `min({value}, {entityB:crit})` (Reduces incoming critical chance by `value`, capped at attacker's crit chance)
Crit Damage Resistance: `min({value}, max(50, {entityB:crit_damage}))` (Reduces incoming crit damage multiplier by `value`, but target's crit_damage is considered at least 50 for this calculation)
Damage Bonus: `{entityA:attack}*{value}/100` (Increases damage by a percentage of base physical damage)
Health Bonus: `{entityA:health}*{value}/100` (Increases max health by a percentage of base vitality/health attribute)
Combat Power Calculation:
Each attribute has a `combatPower` coefficient.
Total Combat Power = Sum of (Attribute Value * Attribute Combat Power Coefficient).
Scripting System: Unleash Your Creativity!
The Scripting System is one of SagaLoreStats' most versatile features. It allows you to use JavaScript to create entirely new attributes, modify existing ones, or implement complex game mechanics that go beyond simple configuration.
Understanding Script Basics Every script file you create needs a few key pieces of information at the top and specific functions.
Script Properties (Defined at the top of your .js file):
priority: (Number) Determines the order of execution. Higher numbers run first. Example: `var priority = 103;`
combatPower: (Number) How much this attribute contributes to the player's overall Combat Power score. Example: `var combatPower = 5.0;`
attributeName: (String) The display name of your attribute. Example: `var attributeName = "My Custom Aura";`
attributeType: (String) The type of attribute. Can be:
`ATTACK_OR_DEFENSE`: For attributes that trigger during combat (attacking or defending).
`OTHER`: For miscellaneous attributes.
`UPDATE`: For attributes that run on a timer (e.g., regeneration).
placeholder: (String) The internal name used for PlaceholderAPI. Example: `var placeholder = "myCustomAura";`
Core Script Functions:
`onLoad(attr)`: Called when the script is loaded (e.g., server start, `/sls reload`). Use this for setup. Must return the `attr` object.
`runAttack(attr, attacker, entity, handle)`: For `ATTACK_OR_DEFENSE` types. Called when a player (attacker) attacks another entity. Return `true` to stop further attribute processing for this event, `false` to continue.
`runDefense(attr, entity, attacker, handle)`: For `ATTACK_OR_DEFENSE` types. Called when a player (entity) is attacked. Return `true` to stop further processing, `false` to continue.
`runUpdate(attr, entity, handle)` or `run(Attr, entity, handle)`: For `UPDATE` types. Called periodically. Return `true` if an action was performed. (Note: The README uses `run` in an example, but `runUpdate` is mentioned elsewhere. Clarify based on plugin's actual expectation or use the one from examples). The example shows `run`.
`getInterval()`: For `UPDATE` types. Returns the update interval in seconds. Example: `return 5.0;` for every 5 seconds.
Handy Scripting Tools (API Objects) Your scripts have access to several helpful API objects:
Attr (AttributeScriptAPI): Your main toolkit for attribute operations.
`Attr.getValue(entity, "AttributeName")`: Get an entity's value for a SagaLoreStats attribute.
`Attr.setValue(entity, "maxHealth", 30.0)`: Set an entity's vanilla Minecraft attribute (e.g., max health).
`Attr.setEntityHealth(entity, 20.0)`: Directly set an entity's current health.
`Attr.getRandomValue(entity, handle)`: Get the value of the *current script's attribute* for the entity.
`Attr.getRandomValue(entity, "PhysicalDefense", handle)`: Get the value of a *specific SagaLoreStats attribute* for the entity.
`Attr.addDamage(attacker, damageAmount, targetEntity)`: Deal damage. [*`Attr.chance(30.0)`]: Returns `true` with a 30% probability.
`Attr.random(1.0, 10.0)`: Get a random decimal between 1.0 and 10.0.
`Attr.randomInt(1, 10)`: Get a random integer between 1 and 10.
AttrHelper (ScriptAttributeHelper): Utility for fetching attribute values.
`AttrHelper.getAttribute(entity, "%sls_maxhealth%")`: Get attribute using PlaceholderAPI format.
`AttrHelper.getAttr(entity, "maxhealth")`: Simpler way to get SagaLoreStats attribute.
`AttrHelper.getAttribute(entity, "attack")`: Get custom SagaLoreStats attribute 'attack'.
AttributeAPI: For manipulating attribute sources on an entity.
`var data = Attr.getData(entity, handle);`: Get the entity's attribute data container.
`AttributeAPI.add(data, "MyEffectSourceName", Arrays.asList("Physical Damage: 10", "Critical Chance: 5%"));`: Add temporary attributes from a source.
`AttributeAPI.take(data, "MyEffectSourceName");`: Remove attributes from that source.
Utils: General utility functions.
`Utils.registerOtherAttribute("Magic Defense", 0.0, "magic_defense_placeholder")`: Register a new attribute type that other scripts or configs can use.
`Utils.sendMessage(player, "§6[SagaLoreStats] §fYour attack dealt §a10 §fdamage!");`: Send a message to a player.
logger: For printing messages to the server console (useful for debugging).
`logger.info("Script loaded!");`
`logger.debug("Player health: " + currentHealth);` (Only shows if debug mode is on)
`logger.warning("Something might be wrong!");`
Example: Creating a "Magic Attack" Script Let's say you want an attribute that deals extra magic damage.
Code (Javascript):
// Script Properties var priority
=60;// Executes after some defense attributes, before base attack var combatPower
=1.5; var attributeName
="Magic Attack"; var attributeType
="ATTACK_OR_DEFENSE"; var placeholder
="magic_attack";
/** * Called when the script loads. * We can register related attributes here, like Magic Defense. */ function onLoad
(Attr
){ Utils.
registerOtherAttribute("Magic Defense",0.0,"magic_defense");// Display Name, Default Value, Placeholder logger.
info("Magic Attack script loaded and Magic Defense registered!"); return Attr
; }
/** * Called when the player (attacker) attacks another entity. */ function runAttack
(Attr
, attacker
, entity
, handle
){ // Get the attacker's "Magic Attack" value from their gear var magicDamageValue
= Attr.
getRandomValue(attacker
, handle
);// 'handle' refers to this script's attribute
if(magicDamageValue
<=0){ returnfalse;// No magic attack value, do nothing }
// Get the target's "Magic Defense" value // Note: "Magic Defense" is the display name we registered in onLoad var magicDefenseValue
= Attr.
getRandomValue(entity
,"Magic Defense", handle
);
// Calculate final magic damage var finalMagicDamage
=Math.
max(0, magicDamageValue
- magicDefenseValue
);
// Send a message to the attacker if they are a player if(attacker
instanceof org.
bukkit.
entity.
Player){ Utils.
sendMessage(attacker
,"§b[Magic] §fYour magic strike dealt an extra §e"+ finalMagicDamage.
toFixed(1)+" §fdamage!"); } // Optionally, message the target too if(entity
instanceof org.
bukkit.
entity.
Player){ Utils.
sendMessage(entity
,"§b[Magic] §fYou were struck by magic for §c"+ finalMagicDamage.
toFixed(1)+" §fdamage!"); } }
returnfalse;// Allow other attack attributes to process }
Example: Creating a "Health Regeneration Per Second" Script This script will heal the player a certain amount every second.
Code (Javascript):
// Script Properties var priority
=10; var combatPower
=0.8; var attributeName
="SecRegen";// Shorter display name var attributeType
="UPDATE"; var placeholder
="health_regen_second";
function onLoad
(Attr
){ logger.
info("Health Regen Per Second script loaded!"); return Attr
; }
/** * Called periodically for players with this attribute. */ function run
(Attr
, entity
, handle
){// 'run' or 'runUpdate' var currentHealth
= entity.
getHealth(); // For players, max health can change due to other attributes. // For other entities, it's usually fixed unless another plugin changes it. var maxHealth
= entity.
getAttribute(org.
bukkit.
attribute.
Attribute.
GENERIC_MAX_HEALTH).
getValue();
if(currentHealth
>= maxHealth
){ returnfalse;// Already at full health }
// Get the "SecRegen" value for this player var healAmount
= Attr.
getRandomValue(entity
, handle
);
if(healAmount
<=0){ returnfalse;// No regeneration amount }
// Optional: Send a subtle message or particle effect // if (entity instanceof org.bukkit.entity.Player) { // Utils.sendActionBar(entity, "§a+" + healAmount.toFixed(1) + " HP"); // }
returntrue;// Indicates an update happened }
/** * How often should the 'run' function be called? * @returns {number} Interval in seconds */ function getInterval
(){ return1.0;// Every 1 second }
Deploying Your Scripts
Create your `.js` file (e.g., `my_magic_attack.js`).
Place it in the `plugins/SagaLoreStats/script/` directory on your server.
Restart your server or run `/sls reload`.
Check the server console for any loading messages or errors from your script (especially those from `logger.info` or `logger.warning`).
Script Configuration (Optional - `script.yml`) You can provide metadata for your scripts in `script.yml`:
Code (YAML):
script:
my_magic_attack: # This should match your JS file name (without .js) enabled: true
description: "Adds magic damage based on item lore." author: "YourName" version: "1.0.1" # config: # You can add custom config values here accessible within the script later if needed # base_particle_effect: "CRIT_MAGIC"
⚙️ Configuration Files SagaLoreStats uses several YAML files for its configuration, located in the `plugins/SagaLoreStats/` folder:
config.yml: Main plugin settings (debug mode, update intervals, etc.).
message.yml: Customizable messages for various plugin actions and attribute triggers.
debugmessage.yml: Messages used specifically when debug mode is enabled.
attribute.yml: Core definitions for non-scripted attributes, their priorities, combat power coefficients, and default formulas.
format.yml: Defines the text patterns (regex) used to recognize attributes in item Lores.
stats.yml: Configuration for the `/sls stats` GUI panel.
script.yml: Configuration and metadata for JavaScript-based attributes.
script/ (directory): Contains your custom JavaScript files for new attributes.
⌨️ Commands & Permissions The main command is `/sls`.
Code (YAML):
Commands:
/sls reload:
Description: Reloads the plugin's configuration files and scripts.
Permission: sagalorestats.admin.reload
/sls stats [player_name]:
Description: Opens the attribute panel for yourself or the specified player.
Permission: sagalorestats.command.stats
(for self
) Permission: sagalorestats.command.stats.other
(for others
) /sls attribute <add|remove> <AttributeName> [Value]:
Description: Adds or removes a Lore attribute to/from the item held in your main hand.
For 'add', AttributeName refers to the key in attribute.yml or your script's placeholder.
The Value is what gets written into the lore, e.g.,
"+10".
Permission: sagalorestats.admin.attribute
/sls debug:
Description: Toggles debug mode on or off for the plugin.
Permission: sagalorestats.admin.debug
Permissions (General):
sagalorestats.admin: Grants access to
all admin commands.
sagalorestats.user: Basic user access
(implicitly granted for attribute effects
).
➕ How to Add New Attributes You have flexible ways to introduce new attributes:
Method 1: Via `attribute.yml` (for Simpler Attributes) This method is best for attributes that can be defined with existing types and formulas.
Open `attribute.yml`.
Define your attribute under the `attribute.key.<type>` section:
Code (YAML):
attribute:
key:
attackOrDefense: # Or 'other', 'update' my_new_stat: "My New Stat Display Name"# Internal key: "Display Name" # ... other attribute types # ...
Set its priority:
Code (YAML):
priority:
attackOrDefense:
my_new_stat: 80
# Example priority # ...
Define a formula (optional, if it needs one different from direct value):
Code (YAML):
formula:
my_new_stat: "{value} * 0.5 + {entityA:attack} * 0.1"# Example: 50% of its value + 10% of attacker's base attack
Add trigger messages in `message.yml` (optional):
Code (YAML):
message:
attribute_trigger:
my_new_stat: -
"&6Your {attribute} &fflares, dealing &e{damage} &fdamage!"# For attacker -
"&cThe enemy's {attribute} &fburns you for &e{damage} &fdamage!"# For victim
If it's a new probability-based attribute, you might need to tell the plugin it's probabilistic. (This usually involves Java code changes in `DamageListener.java`'s `isAttributeTriggered` method as per the original docs, or ensure your script handles the probability via `Attr.chance()`). For config-only, it assumes direct application unless a script handles it.
Reload the plugin: `/sls reload`.
Add lore to an item like: `My New Stat Display Name: +10`
Method 2: Via JavaScript (for Complex/Unique Attributes) This is the most powerful method, perfect for attributes with unique logic, custom effects, or interactions.
Create a new `.js` file in `plugins/SagaLoreStats/script/` (e.g., `elemental_fury.js`).
Write your script code (see the "Scripting System" section above for structure and examples).
Implement `runAttack()`, `runDefense()`, or `run()`/`getInterval()` based on `attributeType`.
Use the provided APIs (`Attr`, `Utils`, `logger`) for logic, effects, and messages.
Optionally, add metadata to `script.yml`.
Reload the plugin: `/sls reload`.
Add lore to an item like: `Elemental Fury: +5` (where "Elemental Fury" is the `attributeName` in your script).
Example: A simple "Mana Burn" script attribute
Code (Javascript):
// plugins/SagaLoreStats/script/mana_burn.js var priority
=78; var combatPower
=2.0; var attributeName
="Mana Burn"; var attributeType
="ATTACK_OR_DEFENSE"; var placeholder
="mana_burn";
function runAttack
(Attr
, attacker
, entity
, handle
){ var burnValue
= Attr.
getRandomValue(attacker
, handle
);// Get "Mana Burn: +X" value if(burnValue
>0&& entity.
getType().
name()==="PLAYER"){// Check if target is a Player // Assuming players have "mana" - for real use, integrate with a mana plugin or use XP levels var targetPlayer
= entity
; var currentMana
= targetPlayer.
getLevel();// Using XP level as pseudo-mana var manaToBurn
=Math.
min(currentMana
, burnValue
);
if(manaToBurn
>0){ targetPlayer.
setLevel(currentMana
- manaToBurn
); if(attacker
instanceof org.
bukkit.
entity.
Player){ Utils.
sendMessage(attacker
,"§dYou burn §5"+ manaToBurn
+" §dof "+ targetPlayer.
getName()+"'s mana!"); } Utils.
sendMessage(targetPlayer
,"§5Your mana was burned by §d"+ manaToBurn
+"§5!"); // You could add a particle effect too // Utils.spawnParticle(entity.getLocation(), "SPELL_WITCH", 10, 0.5, 0.5, 0.5, 0.1); } } returnfalse;// Don't stop other attributes }
To use this: Add `Mana Burn: +5` to an item's lore.
Method 3: Hybrid Approach (Config + Script) Define basic aspects in `attribute.yml` and then enhance or control them with scripts.
Define base attribute names in `attribute.yml` (e.g., `fire_affinity_damage`, `fire_affinity_resistance`).
In a script, fetch these values using `Attr.getRandomValue(entity, "Fire Affinity Damage", handle)` and implement complex interactions, conditional effects, or combine them in unique ways.
This offers a good balance between easy configuration for base values and powerful scripting for nuanced mechanics.
Important Notes
Attribute priorities significantly affect calculation order and combat outcomes. Plan them carefully!
Balance probabilistic attributes (crit, dodge) to avoid unbalancing gameplay.
Design formulas with inter-attribute balance in mind.
Maximum values for attributes can often be capped or managed via formulas or script logic. The original docs mention `value` node in `attribute.yml` for limits, check this.
When scripting, use `Attribute.MAX_HEALTH` for Minecraft's max health attribute. In Java, it would be `Attribute.GENERIC_MAX_HEALTH`.
Project Core Principles
Minimal Vanilla Dependency: Primarily uses vanilla damage and health events as a base. Most attributes are custom-implemented, avoiding reliance on vanilla potion effects or attribute systems for core functionality.
Independent Attribute Management: SagaLoreStats controls the calculation, application, and updating of its custom attributes.
️ Installation & Basic Usage
Download the SagaLoreStats `.jar` file.
Place it into your server's `plugins` folder.
Start (or restart) your server. This will generate the default configuration files.
Modify the configuration files (especially `attribute.yml`, `format.yml`, and potentially `script/` for custom attributes) to suit your server's needs.
Use `/sls reload` to apply configuration changes without a full server restart.
To add attributes to items, edit their Lore in-game using commands from other plugins (like Essentials `/lore add`) or an NBT editor, following the patterns defined in `format.yml` and your attribute definitions. Example Lore line: `Physical Damage: +10-15` or `Critical Chance: +5%`.
Players can check their total attributes using `/sls stats`.