MagicSpells icon indicating copy to clipboard operation
MagicSpells copied to clipboard

Dynamically adjust spell configuration nodes via api.

Open NeumimTo opened this issue 4 years ago • 1 comments

Copying conversation from discord server channel:

NeumimTo 19.12.2020

Hey, I would like to dynamically adjust spell numerical values depending on ingame conditions/player state. So far it seems nearly impossible to achieve such thing.

For example

dodge:
    spell-class: ".buff.DodgeSpell"
    toggle: true
    duration: <value that is unique depending on a player that casts the spell>
    distance: 2
    spells:
        - particleProjectileSpellName

Any chance to make this thing more accessible, or provide necessary hooks that other plugins could use? I looked at the code, and only thing that came to my mind is to somehow make MobConfig to have an unique spell definition per player. eg:

dodge_player1:
    duration: 1000
   ...
dodge_player2:
    duration: 2000

which is a sollution i dont really like

The easiest and least destructive way to allow such thing seems to be editing Spell#getConfigInt/Long/... methods pseudocode

    protected int getConfigInt(String key, int defaultValue, Player caster) {
        return thirdPartyHookEnabled && hook.supportsNode(key) ? callhook(key, caster) : config.getInt("spells." + internalName + '.' + key, defaultValue);
    }

JasperLorelai 19.12.2020 This was something that was planned. We wanted to have MS variable replacement supported in configuration options. I looked into it before and I agree that one of the easiest ways to do that is through the getConfig methods. We'd need to add a player argument and make it read the values every time the spell is casted.

I could help to complete this task if theres someone willing to guide me thru codebase

NeumimTo avatar Jan 05 '21 21:01 NeumimTo

From discord:

JasperLorelai:

You could elaborate on how you plan to tackle this. Point is, nobody has any objections.

General Stuff

My approach would be to create an interface with methods for reading data from Spell.config.

interface SpellDataReader {
       int getConfigInt(LivingEntity, key, defVal);
       long getConfigLong(LivingEntity, key, defVal)
       ... you get the idea
}

Spell.config would also become datatype SpellDataReader

Then provide a default implementation, pretty close to what is in MS only add reference to the caster entity.

XX implements SpellDataReader {

        protected MagicConfig config;

 	protected int getConfigInt(LivingEntity caster, String key, int defaultValue) {
		return config.getInt("spells." + internalName + '.' + key, defaultValue);
	}
	
	protected long getConfigLong(LivingEntity caster, String key, long defaultValue) {
		return config.getLong("spells." + internalName + '.' + key, defaultValue);
	}
	
	protected boolean getConfigBoolean(String key, boolean defaultValue) {
		return config.getBoolean("spells." + internalName + '.' + key, defaultValue);
	}
	

Thirdparty plugins could eventually override these values as needed

YY extends XX {

	protected boolean getConfigBoolean(String key, boolean defaultValue) {
	        if (thirdPartyPluginSupportsKey("spells." + internalName + '.' + key) {
	            return X;
	        }
		return super.getConfigBoolean(key, defaultValue);
	}
	

Since MagicConfig class is no longer within Spell constructor lookup needs to be edited as well.

	try {
		constructor = spellClass.getConstructor(MagicConfig.class, String.class);
	} catch (NoSuchMethodException e) {
		continue;
	}

An api that third party plugins could use to register its own spell data reader into ms would be also required.

Firing with an event, that other plugins could use to pass custom SpellDataReader seems to be sufficient

			try {
        
				 spell = constructor.newInstance(event.getReader == null? default: event.getReader, spellName);
			} catch (Exception e) {

Spells

Lots of spells/listeners that i looked on seems to be trivial to refactor into such system such as AgeSpell that would only need trivial changes

	private void applyAgeChanges(Ageable a, LivingEntity caster) {
		if (setMaturity) {
			int rawAge = getConfigInt(caster, "age", 0);
			a.setAge(rawAge);
		}

		if (applyAgeLock) a.setAgeLock(true);
	}

In this approach one of main problems that i can think of now is validation you do after loading the spell from config.

For example within StunSpell#initialize there is

		if (!stunBody && !stunMonitor) {
			MagicSpells.error("StunSpell '" + internalName + "' is not attempting to stun the body or the monitor.");
			return;
		}

These validations, would no longer work since other plugins may eventually edit each time the spell is executed. These must be moved into spell execution, or abandoned.

This approach would also add some overhead. The spell config values would be read more often from MemoryConfiguration.

NeumimTo avatar Jan 13 '21 17:01 NeumimTo