First of all, if you dont like reflections or reverse engineering this article not for you.
Code included still WIP and some parts "not very good" due to lack of time to benchmark everything.
Expected that you use Forge with SRG names for development.
Story:
In far far away SMP server players were unable to find iridium sample, some users joined dark side, others kept looting with no success.
After 2 hours run i decided that this is fucking korea and must be fixed;
This thread about how to replace TE objects on existing world without loss of devices.
Modding go in two stages:
First you mush ensure that your code executed in proper time, in current case after all mods you wanted to edit already constructed and ready to go, this can be cone by loadafter annotation (for each mod you want to alter) or by forcing your mod to construct last:
private void EnsureLastWord() throws Throwable
{
//this method allow mod to run LAST no matter what
//if other mod used similar technique result is undefined or infinite loop based on exact implementation
Loader load = Loader.instance();
LoadController lc = (LoadController) UnsafeImp.Read_Object(load, "modController");
List<ModContainer> mcsA = lc.getActiveModList();
List<ModContainer> lcn = new ArrayList();
ModContainer last = null;
for (ModContainer x : mcsA)
{
if (x.getName().equals("YOURMODNAMEID"))
{
last = x;
continue;
}
lcn.add(x);
}
lcn.add(last);
UnsafeImp.Write_Object(lc,"activeModList",lcn);
}
Display More
This method resort modlist and place mod with modid "YOURMODNAMEID" to last position of list, this shoud be done only single time (i missed init field soo it called two times but no harm done), calling this method shoud be done at preinit stage.
Main part of code shoud be placed to postinit stage, in other case result in undefined.
Note: "Fetch_FieldEx" is wrapper over reflections that search fields of class and all it's superclasses.
Second stage is actual changes, this done by reflections, when scaning for fields workspace and srg name shoud be checked, in other case your workspace or constructed mod will fail to work:
discover = (HashMap) UnsafeImp.Fetch_FieldEx(TileEntity.class, "nameToClassMap","field_70326_a");
Fetch_FieldEx will discover and read field with given name: nameToClassMap for workspace and field_70326_a for SRG obfuscated enviroment.
To minimize version dependency reflections used only when absolutely necessary, in our case registerTileEntity throw exception if TE with given name already present soo we remove it from Map before registering modded TE:
discover.remove("Replicator");
GameRegistry.registerTileEntity(TileEntityReplicatorImp.class, "Replicator");
TileEntityReplicatorImp.class is custom version of TileEntityReplicator :
public class TileEntityReplicatorImp extends TileEntityReplicator implements IPatternStorage
{
public void scanforPatternStorage()
{
Storage = (IPatternStorage)this;
}
@Override
public short getPatternCount() {
return (short) Recipes.Scanner.getRecipes().size();
}
@Override
public ItemStack getPatternItemstack(int id) {
if (id >= getPatternCount())
return null;
RecipeInputItemStack is = (RecipeInputItemStack) KEYSET[id];
return is.input;
}
static Map<ItemStack,int[]> IREC = new HashMap();
static Object[] KEYSET;
static Object[] VALUES;
{
Map TMP = Recipes.Scanner.getRecipes();
KEYSET = TMP.keySet().toArray();
VALUES = TMP.values().toArray();
for (int i=0; i < KEYSET.length; i++){
RecipeInputItemStack I = (RecipeInputItemStack) KEYSET[i];
RecipeOutput O = (RecipeOutput) VALUES[i];
int[] itm = new int[]
{
O.metadata.getInteger("recordedAmountUU"),
O.metadata.getInteger("recordedAmountEU")
};
IREC.put(I.input, itm);
}
}
@Override
public int[] getPatternvalus(ItemStack id) {
return IREC.get(id);
}
@Override
public boolean transferPattern(ItemStack arg0, int arg1, int arg2) {
return false;
}
}
Display More
Now replicator is pattern storage itself with all registered scanner recipes build in, also it's completely API compatable, soo other mods that want pattern storage also will have access to all recipes.
This change effect only replicators loaded from world save, new placed replicators will keep initial class, this i result of IC2e multiblock implementation, additional TE declaration hardcoded into blockMachine2.class, soo we have no choice expect replace it to:
public class BlockMachine2Imp extends BlockMachine2
{
public BlockMachine2Imp(Configuration config, InternalName internalName) {
super(config, internalName);
}
public TileEntityBlock createTileEntity(World world, int meta)
{
switch (meta)
{
case 0:
return new TileEntityTeleporter();
case 1:
return new TileEntityTesla();
case 2:
return new TileEntityCropmatron();
case 3:
return new TileEntityCentrifuge();
case 4:
return new TileEntityMetalFormer();
case 5:
return new TileEntityOreWashing();
case 6:
return new TileEntityPatternStorage();
case 7:
return new TileEntityScanner();
case 8:
return new TileEntityReplicatorImp();
default:
return null;
}
}
}
Display More
Replacement done in two stages, first we cleanup blocklist:
QuoteBlock.blocksList[Ic2Items.replicator.itemID] = null;
NOTE: Ic2Items.replicator.itemID hold block ID, this can be seen from original constructor's source code.
Without this game will fail to start.
Due to IC2e blockIDassigner bug, leaving item reference will cause game crash without stating that problem with dublicate ID soo we also cleanup item reference:
Now we can register block again, since there is no constructor with ID specification, we must "cheat" IC2 with fake configuration that return value we want:
public class ConfigurationImp extends Configuration {
private Property prop;
public ConfigurationImp(Property prop){
this.prop = prop;
}
public Property get(String category, String key, int defaultValue)
{
return prop;
}
}
Display More
Now we can allocate block again with same ID but different class:
new BlockMachine2Imp(new ConfigurationImp(new Property("block",Ic2Items.replicator.itemID+"",Type.INTEGER)), InternalName.blockMachine2);
Now we can run game, place Replicator and enjoy all recipes embedded initially.
Same actions can be used to replace all types of TE objects including vanilla ones (disabling furnace or changing vanilla chest size anyone?);
ps. same work in bukkit, this allow to a replace nearly everything serverside, if done properly all changes will work smooth in unmodded client.