Sign in to follow this  
Merivo

Modding a spell (CtMethod compilation fail) [Solved]

Recommended Posts

I've been trying to modify a spell to make it's effects more flexible (using a property file).

 

However I've been getting exceptions when I run Mod Loader "compile error: no such class: $4" .. this is repeated for the 4 parameters $1 through $4.

 

I've been following the footsteps of another mod, and I'm stumped as to why mine isn't working.. see the full source below:

 

Spoiler

public class ConfigureDirtMod  implements WurmMod, Configurable, PreInitable {

	public int amount = 6;
	public int castingTime = 10;
	public int cost = 20;
	public int difficulty = 50;
	public int level = 40;
	public long cooldown = ;
	
	@Override
	public void configure(Properties properties) {

		amount = Integer.parseInt(properties.getProperty("giveAmount", Integer.toString(amount)));
		castingTime = Integer.parseInt(properties.getProperty("castingTime", Integer.toString(castingTime)));
		cost = Integer.parseInt(properties.getProperty("cost", Integer.toString(cost)));
		difficulty = Integer.parseInt(properties.getProperty("difficulty", Integer.toString(difficulty)));
		level = Integer.parseInt(properties.getProperty("level", Integer.toString(level)));
		cooldown = Long.parseLong(properties.getProperty("cooldown", Long.toString(cooldown)));
	}

	@Override
	public void preInit() {
		try {
			CtConstructor construct = HookManager.getInstance().getClassPool()
					.getCtClass("com.wurmonline.server.spells.Dirt").getDeclaredConstructor(new CtClass[]{});
			construct.instrument(new Construct());
			
			CtClass skill = HookManager.getInstance().getClassPool().getCtClass("com.wurmonline.server.skills.Skill");
			CtClass creature = HookManager.getInstance().getClassPool().getCtClass("com.wurmonline.server.creatures.Creature");
			CtClass item = HookManager.getInstance().getClassPool().getCtClass("com.wurmonline.server.items.Item");
			CtMethod doEffect = HookManager.getInstance().getClassPool()
					.getCtClass("com.wurmonline.server.spells.Dirt").getDeclaredMethod("doEffect", new CtClass[]{skill,CtClass.doubleType,creature,item});
			doEffect.instrument(new DoEffect());
		} catch (NotFoundException | CannotCompileException e) {
			throw new HookException(e);
		}
	}
	
	class Construct extends ExprEditor {
		public void edit(MethodCall m)
		{
		  //String aName, int aNum, int aCastingTime, int aCost, int aDifficulty, int aLevel, long cooldown
			try {
				m.replace(
				    "super(\"Dirt\", 453, "+castingTime+", "+cost+", "+difficulty+", "+level+", "+cooldown+");"+
				    "targetTile = true;"+
				    "targetItem = true;"+
				    "description = \"creates and destroys dirt\";"
				  );
			} catch (CannotCompileException e) {
				e.printStackTrace();
			}
		}
	}
	
	class DoEffect extends ExprEditor {

		public void edit(MethodCall m)
		{
			try {
				//Skill castSkill, double power, Creature performer, Item target)
				m.replace("int sizeLeft = $4.getFreeVolume();"+
   "try    {"+
     "ItemTemplate template = ItemTemplateFactory.getInstance().getTemplate(26);"+
     
     "boolean created = false;"+
     
     "int nums = Math.min("+amount+", sizeLeft / template.getVolume());"+
     "if ($4.isCrate()) {"+
				"nums = Math.min("+amount+", $4.getRemainingCrateSpace());"+
     "}"+
     "if (nums > 0) {"+
     
				"if ($4.isBulkContainer()) {"+
				

				  "Item dirt = ItemFactory.createItem(26, (float)$2, $3.getName());"+
				  
				  "dirt.setWeight(template.getWeightGrams() * nums, true);"+
				  "dirt.setMaterial(template.getMaterial());"+
				  "if ($4.isCrate()) {"+
				    "dirt.AddBulkItemToCrate($3, $4);"+
				  "} else {"+
				    "dirt.AddBulkItem($3, $4);"+
				  "}"+
				  "created = true; }"+
				
				"else {"+
				
				  "for (int x = 0; x <= nums; x++) {"+
				  
				    "if ($4.getOwnerId() == $3.getWurmId()) {"+
				    
				      "if (!$3.canCarry(template.getWeightGrams())) {"+
				      
				        "if (created) {"+
				          "$3.getCommunicator().sendNormalServerMessage(\"You create some dirt.\", (byte)2);"+
				        "}"+
				      "}"+
				    "}"+
				    "else if (!$4.mayCreatureInsertItem())"+
				    "{"+
				      "if (created)"+
				        "$3.getCommunicator().sendNormalServerMessage(\"You create some dirt.\", (byte)2);"+
				      "$_ = null;"+
				    "}"+
				    "Item dirt = ItemFactory.createItem(26, (float)$2, $3.getName());"+
				    "$4.insertItem(dirt);"+
				    "created = true;"+
				  "}"+
				"}"+
     "}"+
     "if (created) {"+
				"$3.getCommunicator().sendNormalServerMessage(\"You create some dirt.\", (byte)2);"+
     "}	    }"+
   "catch (NoSuchTemplateException localNoSuchTemplateException) {}catch (FailedException localFailedException) {}");
			} catch (CannotCompileException e) {
				e.printStackTrace();
			}
		}
	}
}

 

 

 

Edited by Merivo
Problem Solved

Share this post


Link to post
Share on other sites

I'm not exactly sure what's wrong but I think I see a problem.

 

http://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#add

 

read the part about "Substituting source text for an existing expression" which is relavent to what your doing.

1. I don't think try catch is supported within the "m.replace(~~stuff~~)".

2. You may need to surround your code with {} as it's a block...so m.replace("{ ~~ stuff ~~ }");

 

I usually take a different path and try to make methods in my mod using full on Java code so my IDE works with the code. Then, I use setbody to copy the method from the mod and overwrite the method in WU. I did this in loadingUnchainedMod regarding setfire...

/**
     * This method is a reference method used by ctmSetFire to copy and replace the same method in WU.
     * @param performer is type Creature and is the entity doing the action.
     * @param target is type Item and is the item where fire is to be set.
     * @return success indicator. The modded equivalency of this is that it always succeeds.
     */

Share this post


Link to post
Share on other sites
			doEffect.instrument(new DoEffect());

This will not replace the method "doEffect". It will replace any method call in doEffect with your code. This is probabaly failing if there are method calls with less than 4 arguments.

 

To replace the complete method with different code you'll need doEffect.setBody(code) or copy an existing method with setBody(ctOtherMethod, classMap) as joedobo said.

Share this post


Link to post
Share on other sites

Thanks for the replies! Sorry about the delay, I was out all day yesterday. If try/catch isn't supported, is throws{}?

 

As for your different approach joedobo, I would certainly rather do it the way you suggest but the only thing I can't figure out is how to give doEffect the value of my variable amount; since it's going in a different class how's that going to work?

 

Back to the original approach, I've wrapped {} around the method, and am now using setbody. That fixed the old exceptions, now I'm getting a new exception: "CannotCompileException: no such class: ItemTemplate". I didn't come up with that part of the code myself, this was from the original Dirt.class, shouldn't it already have imported ItemTemplate? Or is it that my ConfigureDirtMod needs to import ItemTemplate?

 

Oh and, quick question. Which is called first, configure or preInit?

Edited by Merivo
Grammar

Share this post


Link to post
Share on other sites

So, I've been having a look at your LoadingUnchainedMod source, and this code throws an exception when I try this out in my own class, but not in yours:

ClassPool pool = HookManager.getInstance().getClassPool();

CtClass ctcSelf = pool.makeClass("Default");
try {
	ctcSelf = pool.get(this.getClass().getName());
} catch (NotFoundException e) {
	e.printStackTrace();
}

 

javassist.NotFoundException: org.gotti.wurmunlimited.mods.configuredirt.ConfigureDirtMod..

 

Any ideas?

Share this post


Link to post
Share on other sites

The modloader calls configure() for all mods, then preInit(), then init().

 

The import statements are only used in the compiler. The resulting bytecode will use full class names and all info about imported packages is lost. So you'll either have to write the full qualified name in setBody() or tell the class pool that you want to use imports:
 pool.importPackage("com.wurmonline.server.spells")

 

The class pool is only set up to cover the main class loader. By default all modules are loaded in their own classloader unless the sharedClassLoader property is set in the module configuration. This helps to isolate modules so they can't interfere with eachother. But this also means that any classes from the module are unavailable from the core server code.

 

 

Share this post


Link to post
Share on other sites

Thank you very much ago, that information was very helpful! The mod's now working :)

 

Share this post


Link to post
Share on other sites

Just read this. Ago is helpful. He also helped me a lot when I first started. It's nice when someone knows exactly why something doesn't work.

 

Regarding the way I referred too, I prefer to have java code that my ide can inspect. Java code as quoted strings is a hassle. Merivo, it seems you got things working so this may not be that useful. but, I make methods in my mods that basically could be directly copied and pasted into WU with the one stipulation that the ClassMap will replace all occurences of my mod's class name with the WU class I'm putting the mod in. In order to do this I need to use the method.setbody(CtMethod method, ClassMap map). I don't know of another way to get a CtMethod object other than through CtClass. Getting a CtClass and CtMethod object for the mod requires using the shared class loader.

 

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this