package mod.sin.wyvern; import com.wurmonline.server.*; import com.wurmonline.server.behaviours.Vehicle; import com.wurmonline.server.behaviours.Vehicles; import com.wurmonline.server.bodys.Wound; import com.wurmonline.server.creatures.Creature; import com.wurmonline.server.creatures.CreatureTemplateIds; import com.wurmonline.server.creatures.Creatures; import com.wurmonline.server.deities.Deities; import com.wurmonline.server.economy.Economy; import com.wurmonline.server.items.*; import com.wurmonline.server.creatures.Creatures; import com.wurmonline.server.creatures.NoSuchCreatureException; import com.wurmonline.server.items.Item; import com.wurmonline.server.items.ItemTemplate; import com.wurmonline.server.items.SimpleCreationEntry; import com.wurmonline.server.players.Player; import com.wurmonline.server.players.PlayerInfo; import com.wurmonline.server.players.PlayerInfoFactory; import com.wurmonline.server.skills.Skill; import com.wurmonline.server.skills.SkillList; import com.wurmonline.server.villages.Village; import com.wurmonline.server.webinterface.WcKingdomChat; import com.wurmonline.server.zones.VolaTile; import com.wurmonline.server.zones.Zones; import com.wurmonline.shared.constants.Enchants; import com.wurmonline.shared.util.StringUtilities; import javassist.*; import javassist.bytecode.Descriptor; import javassist.expr.ExprEditor; import javassist.expr.FieldAccess; import javassist.expr.MethodCall; import mod.sin.items.ChaosCrystal; import mod.sin.items.EnchantersCrystal; import mod.sin.items.SealedMap; import mod.sin.kingdomoffices.ItemCreator; import mod.sin.lib.Util; import mod.sin.wyvern.util.TraderItem; import org.gotti.wurmunlimited.modloader.ReflectionUtil; import org.gotti.wurmunlimited.modloader.classhooks.HookException; import org.gotti.wurmunlimited.modloader.classhooks.HookManager; import org.gotti.wurmunlimited.modsupport.ModSupportDb; import org.gotti.wurmunlimited.modsupport.vehicles.ModVehicleBehaviours; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; public class MiscChanges { public static Logger logger = Logger.getLogger(MiscChanges.class.getName()); public static void sendServerTabMessage(String channel, final String message, final int red, final int green, final int blue){ // WARNING: Never change this from a new Runnable. Lambdas are a lie and will break everything. Runnable r = new Runnable() { public void run() { Message mess; for (Player rec : Players.getInstance().getPlayers()) { mess = new Message(rec, (byte) 16, "Server", message, red, green, blue); rec.getCommunicator().sendMessage(mess); } } }; r.run(); } public static void sendGlobalFreedomChat(final Creature sender, final String message, final int red, final int green, final int blue){ sendGlobalFreedomChat(sender, sender.getNameWithoutPrefixes(), message, red, green, blue); } public static void sendGlobalFreedomChat(final Creature sender, final String name, final String message, final int red, final int green, final int blue){ Runnable r = () -> { Message mess; for(Player rec : Players.getInstance().getPlayers()){ mess = new Message(sender, (byte)10, "GL-Freedom", "<"+name+"> "+message, red, green, blue); rec.getCommunicator().sendMessage(mess); } if (message.trim().length() > 1) { WcKingdomChat wc = new WcKingdomChat(WurmId.getNextWCCommandId(), sender.getWurmId(), name, message, false, (byte) 4, red, green, blue); if (!Servers.isThisLoginServer()) { wc.sendToLoginServer(); } else { wc.sendFromLoginServer(); } } }; r.run(); } public static void broadCastDeathsPvE(Player player, Map attackers){ StringBuilder attackerString = new StringBuilder(); final long now = System.currentTimeMillis(); for (final Long attackerId : attackers.keySet()) { final Long time = attackers.get(attackerId); try { final Creature creature = Creatures.getInstance().getCreature(attackerId); if (now - time >= 600000L) { continue; } if(attackerString.length() > 0){ attackerString.append(" "); } attackerString.append(StringUtilities.raiseFirstLetter(creature.getName())); if (creature.isPlayer()) { return; } } catch (NoSuchCreatureException ignored) {} } Players.getInstance().broadCastDeathInfo(player, attackerString.toString()); } public static void broadCastDeaths(Creature player, String slayers){ String slayMessage = "slain by "; sendGlobalFreedomChat(player, slayMessage+slayers, 200, 25, 25); addPlayerStatsDeath(player.getName()); addPlayerStatsKill(slayers); } public static void addPlayerStat(String playerName, String stat){ Connection dbcon; PreparedStatement ps; try { dbcon = ModSupportDb.getModSupportDb(); ps = dbcon.prepareStatement("UPDATE PlayerStats SET "+stat+" = "+stat+" + 1 WHERE NAME = \""+playerName+"\""); ps.executeUpdate(); ps.close(); } catch (SQLException e) { throw new RuntimeException(e); } } public static void addPlayerStatsDeath(String playerName){ Connection dbcon; PreparedStatement ps; try { dbcon = ModSupportDb.getModSupportDb(); ps = dbcon.prepareStatement("UPDATE PlayerStats SET DEATHS = DEATHS + 1 WHERE NAME = \""+playerName+"\""); ps.executeUpdate(); ps.close(); } catch (SQLException e) { throw new RuntimeException(e); } } public static void addPlayerStatsKill(String slayers){ String[] slayerNames = slayers.split(" "); Connection dbcon; PreparedStatement ps; try { dbcon = ModSupportDb.getModSupportDb(); for(String slayer : slayerNames) { if(slayer.length() < 2) continue; ps = dbcon.prepareStatement("UPDATE PlayerStats SET KILLS = KILLS + 1 WHERE NAME = \"" + slayer + "\""); ps.executeUpdate(); ps.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } public static boolean checkMayorCommand(Item item, Creature creature){ if(Servers.localServer.PVPSERVER){ return false; } PlayerInfo pinf = PlayerInfoFactory.getPlayerInfoWithWurmId(item.getLastOwnerId()); if(pinf != null){ if(pinf.getLastLogout() < System.currentTimeMillis()-TimeConstants.DAY_MILLIS*7){ if(creature.getCitizenVillage() != null){ Village v = creature.getCitizenVillage(); if(v.getMayor().getId() == creature.getWurmId()){ VolaTile vt = Zones.getTileOrNull(item.getTilePos(), item.isOnSurface()); if(vt != null && vt.getVillage() != null && vt.getVillage() == v){ return true; } } } } } return false; } public static float getFoodOpulenceBonus(Item food){ float mult = 1.0f; if(food.getSpellEffectPower(Enchants.BUFF_OPULENCE) > 0f){ mult *= 1.0f + (food.getSpellEffectPower(Enchants.BUFF_OPULENCE)*0.0025f); } return food.getFoodComplexity()*mult; } public static float boatSpeedBonus(Vehicle boat) { try { Player captain = Players.getInstance().getPlayer(boat.pilotId); if (captain.getDeity().getNumber() != Deities.DEITY_VYNORA) { return 1; } if (captain.isOffline()) { return 1; } else { if (captain.getFaith() >= 60.0) { return 1.2f; } return 1; } } catch (NoSuchPlayerException e) { e.printStackTrace(); return 1; } } public static void buffBoat(Vehicle boat, Item item) { try { Map newBoatSpeeds = new HashMap<>(); newBoatSpeeds.put(ItemList.caravel, 20f); if(!item.isBoat() || newBoatSpeeds.get(item.getTemplateId()) == null) { return; } float newWindImpact = newBoatSpeeds.get(item.getTemplateId()); logger.info("BoatBuff: Changing the wind impact of " + item.getName() + " from " + boat.getWindImpact() + " to " + newWindImpact + "."); Class vehicleClass = boat.getClass(); float newWindMultiplier = Math.abs(newWindImpact - boat.getWindImpact()) / 10; float oldSpeed = ReflectionUtil.getPrivateField(boat, ReflectionUtil.getField(vehicleClass, "maxSpeed")); ReflectionUtil.setPrivateField(boat, ReflectionUtil.getField(vehicleClass, "maxSpeed"), oldSpeed * newWindMultiplier); ReflectionUtil.callPrivateMethod(boat, ReflectionUtil.getMethod(vehicleClass, "setWindImpact"), (byte)newWindImpact); byte wi = ReflectionUtil.getPrivateField(boat, ReflectionUtil.getField(vehicleClass, "windImpact")); float ns = ReflectionUtil.getPrivateField(boat, ReflectionUtil.getField(vehicleClass, "maxSpeed")); logger.info("BoatBuff: The new max speed of " + item.getName() + " is " + ns + " and the new wind impact is " + wi); } catch (IllegalAccessException | NoSuchFieldException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } } public static long getBedBonus(long secs, long bed){ Optional beds = Items.getItemOptional(bed); if(beds.isPresent()) { Item bedItem = beds.get(); if(bedItem.isBed()){ secs *= 1+(bedItem.getCurrentQualityLevel()*0.005f); } } secs *= 2; return secs; } public static boolean royalSmithImprove(Creature performer, Skill improve){ if(performer.isRoyalSmith()){ if(improve.getNumber() == SkillList.SMITHING_ARMOUR_CHAIN || improve.getNumber() == SkillList.SMITHING_ARMOUR_PLATE || improve.getNumber() == SkillList.SMITHING_BLACKSMITHING || improve.getNumber() == SkillList.SMITHING_GOLDSMITHING || improve.getNumber() == SkillList.SMITHING_LOCKSMITHING || improve.getNumber() == SkillList.SMITHING_METALLURGY || improve.getNumber() == SkillList.SMITHING_SHIELDS || improve.getNumber() == SkillList.SMITHING_WEAPON_BLADES || improve.getNumber() == SkillList.SMITHING_WEAPON_HEADS){ return true; } } return false; } public static void restockTrader(Creature trader) { try { if(trader.getTemplate().getTemplateId() != CreatureTemplateIds.SALESMAN_CID || Economy.getEconomy().getShop(trader).isPersonal()) { return; } int count = 0; Item item; // Hashset of trader items Set toRestock = new HashSet<>(); // Sleep powder added toRestock.add(new TraderItem(ItemList.sleepPowder, 99, false, 99, 99, 20)); logger.info("Adding sleep powder to trader restock list"); // Kingdom tokens added toRestock.add(new TraderItem(ItemCreator.KINGDOM_TOKEN.getTemplateId(), 99, false, 99, 99, 20)); logger.info("Adding kingdom tokens to trader restock list"); // Sealed maps added, use ranom qualities between 10 and 100 (inclusive) toRestock.add(new TraderItem(SealedMap.templateId, 99, true, 10, 101, 3)); logger.info("Adding sealed maps to trader restock list"); toRestock.add(new TraderItem(EnchantersCrystal.templateId, 99, true, 10, 101, 10)); logger.info("Adding enchant crystals to trader restock list"); toRestock.add(new TraderItem(ChaosCrystal.templateId, 99, true, 10, 31, 10)); logger.info("Adding chaos crystals to trader restock list"); for (TraderItem tItem : toRestock) { logger.info("Counting number of " + ItemTemplateFactory.getInstance().getTemplate(tItem.getTemplateid()).getName() + " instances in trader inventory."); count = 0; for (Item i : trader.getInventory().getItemsAsArray()) { if (i.getTemplateId() == tItem.getTemplateid()) { count++; } } logger.info("Trader had " + count + " " + ItemTemplateFactory.getInstance().getTemplate(tItem.getTemplateid()).getName() + "."); while (count < tItem.getQuantity()) { item = Creature.createItem(tItem.getTemplateid(), tItem.getQuality()); trader.getInventory().insertItem(item); count++; } } } catch (Exception e) { e.printStackTrace(); } } public static int getNewFoodFill(float qlevel){ float startPercent = 0.004f; float endPercent = 0.015f; return (int) ((startPercent*(1f-qlevel/100f)+endPercent*(qlevel/100f))*65535); } public static boolean rollRarityImprove(Item source, int usedWeight){ int templateWeight = source.getTemplate().getWeightGrams(); float percentUsage = (float) usedWeight / (float) templateWeight; float chance = percentUsage * 0.05f; if(Server.rand.nextFloat() < chance){ return true; } return false; } protected static final int rarityChance = 3600; protected static HashMap pseudoMap = new HashMap<>(); public static boolean getRarityWindowChance(long wurmid){ //nextInt checks against 0. False is true, true is false. if(pseudoMap.containsKey(wurmid)){ int currentChance = pseudoMap.get(wurmid); boolean success = Server.rand.nextInt(currentChance) == 0; if(success){ pseudoMap.put(wurmid, currentChance+rarityChance-1); }else{ pseudoMap.put(wurmid, currentChance-1); } return !success; }else{ pseudoMap.put(wurmid, rarityChance-1); return !(Server.rand.nextInt(rarityChance) == 0); } } public static byte getNewCreationRarity(SimpleCreationEntry entry, Item source, Item target, ItemTemplate template){ if(source.getRarity() > 0 || target.getRarity() > 0) { byte sRarity = source.getRarity(); byte tRarity = target.getRarity(); int sourceid = entry.getObjectSource(); int targetid = entry.getObjectTarget(); Item realSource = null; if(source.getTemplateId() == sourceid){ realSource = source; }else if(target.getTemplateId() == sourceid){ realSource = target; } Item realTarget = null; if(source.getTemplateId() == targetid){ realTarget = source; }else if(target.getTemplateId() == targetid){ realTarget = target; } if (entry.depleteSource && entry.depleteTarget) { int min = Math.min(sRarity, tRarity); int max = Math.max(sRarity, tRarity); return (byte) (min+Server.rand.nextInt(1+(max-min))); } if(realSource == null || realTarget == null){ logger.info("Null source or target."); return 0; } if(entry.depleteSource && realSource.getRarity() > 0){ int templateWeight = realSource.getTemplate().getWeightGrams(); int usedWeight = entry.getSourceWeightToRemove(realSource, realTarget, template, false); float percentUsage = (float) usedWeight / (float) templateWeight; float chance = percentUsage * 0.05f; if(Server.rand.nextFloat() < chance){ return realSource.getRarity(); } }else if(entry.depleteTarget && realTarget.getRarity() > 0){ int templateWeight = realTarget.getTemplate().getWeightGrams(); int usedWeight = entry.getTargetWeightToRemove(realSource, realTarget, template, false); float percentUsage = (float) usedWeight / (float) templateWeight; float chance = percentUsage * 0.05f; if(Server.rand.nextFloat() < chance){ return target.getRarity(); } } } return 0; } public static void preInit(){ try{ ClassPool classPool = HookManager.getInstance().getClassPool(); final Class thisClass = MiscChanges.class; String replace; // - Create Server tab with initial messages - // CtClass ctVehicle = classPool.get("com.wurmonline.server.behaviours.Vehicle"); CtClass ctPlayers = classPool.get("com.wurmonline.server.Players"); CtMethod m = ctPlayers.getDeclaredMethod("sendStartGlobalKingdomChat"); String infoTabTitle = "Server"; // Initial messages: String[] infoTabLine = {"Server Thread: https://forum.wurmonline.com/index.php?/topic/162067-revenant-modded-pvepvp-3x-action-new-skillgain/", "Website/Maps: https://www.sarcasuals.com/", "Server Discord: https://discord.gg/r8QNXAC", "Server Data: https://docs.google.com/spreadsheets/d/1yjqTHoxUan4LIldI3jgrXZgXj1M2ENQ4MXniPUz0rE4", "Server Wiki/Documentation: https://docs.google.com/document/d/1GeaygilS-Z-d1TuGB7awOe9sJNV4o5BTZw_a2ATJy98"}; StringBuilder str = new StringBuilder("{" + " com.wurmonline.server.Message mess;"); for (String anInfoTabLine : infoTabLine) { str.append(" mess = new com.wurmonline.server.Message(player, (byte)16, \"").append(infoTabTitle).append("\",\"").append(anInfoTabLine).append("\", 0, 255, 0);").append(" player.getCommunicator().sendMessage(mess);"); } str.append("}"); m.insertAfter(str.toString()); // - Enable bridges to be built inside/over/through houses - // CtClass ctPlanBridgeChecks = classPool.get("com.wurmonline.server.structures.PlanBridgeChecks"); replace = "{ return new com.wurmonline.server.structures.PlanBridgeCheckResult(false); }"; Util.setBodyDeclared(thisClass, ctPlanBridgeChecks, "checkForBuildings", replace); // - Disable mailboxes from being used while loaded - // CtClass ctItem = classPool.get("com.wurmonline.server.items.Item"); replace = "$_ = $proceed($$);" + "com.wurmonline.server.items.Item theTarget = com.wurmonline.server.Items.getItem(targetId);" + "if(theTarget != null && theTarget.getTemplateId() >= 510 && theTarget.getTemplateId() <= 513){" + " if(theTarget.getTopParent() != theTarget.getWurmId()){" + " mover.getCommunicator().sendNormalServerMessage(\"Mailboxes cannot be used while loaded.\");" + " return false;" + " }" + "}"; Util.instrumentDeclared(thisClass, ctItem, "moveToItem", "getOwnerId", replace); // - Enable creature custom colors - (Used for creating custom color creatures eg. Lilith) - // CtClass ctCreature = classPool.get("com.wurmonline.server.creatures.Creature"); /*replace = "{ return true; }"; Util.setBodyDeclared(thisClass, ctCreature, "hasCustomColor", replace);*/ // - Increase the amount of checks for new unique spawns by 5x - // CtClass ctServer = classPool.get("com.wurmonline.server.Server"); replace = "for(int i = 0; i < 5; i++){" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctServer, "run", "checkDens", replace); // - Add Facebreyker to the list of spawnable uniques - // CtClass ctDens = classPool.get("com.wurmonline.server.zones.Dens"); replace = "com.wurmonline.server.zones.Dens.checkTemplate(2147483643, whileRunning);"; Util.insertBeforeDeclared(thisClass, ctDens, "checkDens", replace); //ctDens.getDeclaredMethod("checkDens").insertAt(0, "com.wurmonline.server.zones.Dens.checkTemplate(2147483643, whileRunning);"); // - Announce player titles in the Server tab - // CtClass ctPlayer = classPool.get("com.wurmonline.server.players.Player"); replace = "$_ = $proceed($$);" + "if(!com.wurmonline.server.Servers.localServer.PVPSERVER && this.getPower() < 1){" + " "+MiscChanges.class.getName()+".sendServerTabMessage(\"event\", this.getName()+\" just earned the title of \"+title.getName(this.isNotFemale())+\"!\", 200, 100, 0);" + "}"; Util.instrumentDeclared(thisClass, ctPlayer, "addTitle", "sendNormalServerMessage", replace); // - Make leather not suck even after it's able to be combined. - // CtClass ctMethodsItems = classPool.get("com.wurmonline.server.behaviours.MethodsItems"); replace = "if(com.wurmonline.server.behaviours.MethodsItems.getImproveTemplateId(target) != 72){" + " $_ = $proceed($$);" + "}else{" + " $_ = false;" + "}"; Util.instrumentDeclared(thisClass, ctMethodsItems, "improveItem", "isCombine", replace); // - Check new improve materials - // replace = "int temp = "+ItemMod.class.getName()+".getModdedImproveTemplateId($1);" + "if(temp != -10){" + " return temp;" + "}"; Util.insertBeforeDeclared(thisClass, ctMethodsItems, "getImproveTemplateId", replace); // - Remove fatiguing actions requiring you to be on the ground - // CtClass ctAction = classPool.get("com.wurmonline.server.behaviours.Action"); CtConstructor[] ctActionConstructors = ctAction.getConstructors(); for(CtConstructor constructor : ctActionConstructors) { constructor.instrument(new ExprEditor() { public void edit(MethodCall m) throws CannotCompileException { if (m.getMethodName().equals("isFatigue")) { m.replace("" + "if(com.wurmonline.server.Servers.localServer.PVPSERVER){" + " if(!com.wurmonline.server.behaviours.Actions.isActionDestroy(this.getNumber())){" + " $_ = false;" + " }else{" + " $_ = $proceed($$);" + " }" + "}else{" + " $_ = false;" + "}"); logger.info("Set isFatigue to false in action constructor."); } } }); } Util.setReason("Fix Portal Issues."); CtClass ctPortal = classPool.get("com.wurmonline.server.questions.PortalQuestion"); Util.instrumentDeclared(thisClass, ctPortal, "sendQuestion", "willLeaveServer", "$_ = true;"); Util.setReason("Fix Portal Issues."); Util.instrumentDeclared(thisClass, ctPortal, "sendQuestion", "getKnowledge", "$_ = true;"); Util.setReason("Disable the minimum 0.01 damage on shield damage, allowing damage modifiers to rule."); CtClass ctCombatHandler = classPool.get("com.wurmonline.server.creatures.CombatHandler"); replace = "if($1 < 0.5f){" + " $_ = $proceed((float) 0, (float) $2);" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctCombatHandler, "checkShield", "max", replace); // - Allow GM's to bypass the 5 second emote sound limit. - // replace = "if(this.getPower() > 0){" + " return true;" + "}"; Util.insertBeforeDeclared(thisClass, ctPlayer, "mayEmote", replace); // - Make creatures wander slightly if they are shot from afar by an arrow - // CtClass ctArrows = classPool.get("com.wurmonline.server.combat.Arrows"); replace = "if(!defender.isPathing()){" + " defender.startPathing(com.wurmonline.server.Server.rand.nextInt(100));" + "}" + "$_ = $proceed($$);"; Util.instrumentDeclared(thisClass, ctArrows, "addToHitCreature", "addAttacker", replace); Util.setReason("Broadcast death tabs to GL-Freedom."); replace = MiscChanges.class.getName()+".broadCastDeaths($1, $2);"; Util.insertBeforeDeclared(thisClass, ctPlayers, "broadCastDeathInfo", replace); Util.setReason("Broadcast player death tabs always."); replace = MiscChanges.class.getName()+".broadCastDeathsPvE($0, $0.attackers);"; Util.insertBeforeDeclared(thisClass, ctPlayer, "modifyRanking", replace); Util.setReason("Disable PvP only death tabs."); replace = "$_ = true;"; Util.instrumentDeclared(thisClass, ctPlayers, "broadCastDeathInfo", "isThisAPvpServer", replace); Util.setReason("Attempt to prevent libila from losing faith when crossing servers."); CtClass ctIntraServerConnection = classPool.get("com.wurmonline.server.intra.IntraServerConnection"); ctIntraServerConnection.getDeclaredMethod("savePlayerToDisk").instrument(new ExprEditor() { @Override public void edit(FieldAccess fieldAccess) throws CannotCompileException { if (Objects.equals("PVPSERVER", fieldAccess.getFieldName())){ fieldAccess.replace("$_ = false;"); logger.info("Instrumented PVPSERVER = false for Libila faith transfers."); } } }); ctIntraServerConnection.getDeclaredMethod("savePlayerToDisk").instrument(new ExprEditor() { @Override public void edit(FieldAccess fieldAccess) throws CannotCompileException { if (Objects.equals("HOMESERVER", fieldAccess.getFieldName())){ fieldAccess.replace("$_ = false;"); logger.info("Instrumented HOMESERVER = false for Libila faith transfers."); } } }); Util.setReason("Increase food affinity to give 30% increased skillgain instead of 10%."); CtClass ctSkill = classPool.get("com.wurmonline.server.skills.Skill"); CtClass[] params4 = { CtClass.doubleType, CtClass.booleanType, CtClass.floatType, CtClass.booleanType, CtClass.doubleType }; String desc4 = Descriptor.ofMethod(CtClass.voidType, params4); replace = "int timedAffinity = (com.wurmonline.server.skills.AffinitiesTimed.isTimedAffinity(pid, this.getNumber()) ? 2 : 0);" + "advanceMultiplicator *= (double)(1.0f + (float)timedAffinity * 0.1f);" + "$_ = $proceed($$);"; Util.instrumentDescribed(thisClass, ctSkill, "alterSkill", desc4, "hasSleepBonus", replace); Util.setReason("Double the rate at which charcoal piles produce items."); CtClass[] params5 = { CtClass.booleanType, CtClass.booleanType, CtClass.longType }; String desc5 = Descriptor.ofMethod(CtClass.booleanType, params5); replace = "this.createDaleItems();" + "decayed = this.setDamage(this.damage + 1.0f * this.getDamageModifier());" + "$_ = $proceed($$);"; Util.instrumentDescribed(thisClass, ctItem, "poll", desc5, "createDaleItems", replace); Util.setReason("Allow traders to display more than 9 items of a single type."); CtClass ctTradeHandler = classPool.get("com.wurmonline.server.creatures.TradeHandler"); ctTradeHandler.getDeclaredMethod("addItemsToTrade").instrument(new ExprEditor(){ public void edit(MethodCall m) throws CannotCompileException { if(m.getMethodName().equals("size") && m.getLineNumber() > 200){ // I don't think the line number check matters, but I'm leaving it here anyway. m.replace("$_ = 1;"); logger.info("Instrumented size for trades to allow traders to show more than 9 items at a time."); } } }); // -- Identify players making over 10 commands per second and causing the server log message -- // CtClass ctCommunicator = classPool.get("com.wurmonline.server.creatures.Communicator"); replace = "$_ = $proceed($$);" + "if(this.player != null){" + " logger.info(\"Potential player macro: \"+this.player.getName()+\" [\"+this.commandsThisSecond+\" commands]\");" + "}"; Util.instrumentDeclared(thisClass, ctCommunicator, "reallyHandle_CMD_ITEM_CREATION_LIST", "log", replace); //1f+0.5f*(1f-Math.pow(2, -Math.pow((eff-1f), pow1)/pow2)) Util.setReason("Fix 100+ quality or power making certain interaction broken."); replace = "{" + "double pow1 = 1.0;" + "double pow2 = 3.0;" + "double newEff = $1 >= 1.0 ? 1.0+0.5*(1.0-Math.pow(2.0, -Math.pow(($1-1.0), pow1)/pow2)) : Math.max(0.05, 1.0 - (1.0 - $1) * (1.0 - $1));" + "return newEff;" + "}"; Util.setBodyDeclared(thisClass, ctServer, "getBuffedQualityEffect", replace); // double advanceMultiplicator, boolean decay, float times, boolean useNewSystem, double skillDivider) CtClass[] params = { CtClass.doubleType, CtClass.booleanType, CtClass.floatType, CtClass.booleanType, CtClass.doubleType }; String desc = Descriptor.ofMethod(CtClass.voidType, params); double minRate = 1.0D; double maxRate = 8.0D; double newPower = 2.5; Util.setReason("Adjust skill rate to a new, dynamic rate system."); replace = "double minRate = " + String.valueOf(minRate) + ";" + "double maxRate = " + String.valueOf(maxRate) + ";" + "double newPower = " + String.valueOf(newPower) + ";" + "$1 = $1*(minRate+(maxRate-minRate)*Math.pow((100-this.knowledge)*0.01, newPower));"; Util.insertBeforeDescribed(thisClass, ctSkill,"alterSkill", desc, replace); Util.setReason("Reduce chance of lockpicks breaking."); replace = "$_ = 40f + $proceed($$);"; Util.instrumentDeclared(thisClass, ctMethodsItems, "checkLockpickBreakage", "getCurrentQualityLevel", replace); CtClass ctTileBehaviour = classPool.get("com.wurmonline.server.behaviours.TileBehaviour"); CtMethod[] ctGetBehavioursFors = ctTileBehaviour.getDeclaredMethods("getBehavioursFor"); for(CtMethod method : ctGetBehavioursFors){ method.instrument(new ExprEditor(){ public void edit(MethodCall m) throws CannotCompileException { if (m.getMethodName().equals("getKingdomTemplateId")) { m.replace("$_ = 3;"); } } }); } CtClass ctMethodsStructure = classPool.get("com.wurmonline.server.behaviours.MethodsStructure"); Util.setReason("Allow players to construct larger houses."); float carpentryMultiplier = 2f; replace = "if(!com.wurmonline.server.Servers.localServer.PVPSERVER){" + " $_ = $proceed($$)*"+String.valueOf(carpentryMultiplier)+";" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctMethodsStructure, "hasEnoughSkillToExpandStructure", "getKnowledge", replace); Util.setReason("Allow players to construct larger houses."); Util.instrumentDeclared(thisClass, ctMethodsStructure, "hasEnoughSkillToContractStructure", "getKnowledge", replace); Util.setReason("Reduce power of imbues."); replace = "$_ = Math.max(-80d, -80d+$2);"; Util.instrumentDeclared(thisClass, ctMethodsItems, "smear", "max", replace); Util.setReason("Update vehicle speeds reliably."); replace = "if($1 == 8){" + " $_ = 0;" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctPlayer, "checkVehicleSpeeds", "nextInt", replace); Util.setReason("Reduce mailing costs by 90%."); CtClass ctMailSendConfirmQuestion = classPool.get("com.wurmonline.server.questions.MailSendConfirmQuestion"); replace = "$_ = $_ / 10;"; Util.insertAfterDeclared(thisClass, ctMailSendConfirmQuestion, "getCostForItem", replace); Util.setReason("Remove guard tower guards helping against certain types of enemies."); CtClass ctGuardTower = classPool.get("com.wurmonline.server.kingdom.GuardTower"); replace = "if($0.isUnique() || "+Titans.class.getName()+".isTitan($0) || "+RareSpawns.class.getName()+".isRareCreature($0)){" + " $_ = false;" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctGuardTower, "alertGuards", "isWithinTileDistanceTo", replace); // Enable Strongwall for Libila and other spells on PvE CtClass ctSpellGenerator = classPool.get("com.wurmonline.server.spells.SpellGenerator"); ctSpellGenerator.getDeclaredMethod("createSpells").instrument(new ExprEditor() { @Override public void edit(FieldAccess fieldAccess) throws CannotCompileException { if (Objects.equals("PVPSERVER", fieldAccess.getFieldName())) fieldAccess.replace("$_ = true;"); logger.info("Instrumented SpellGenerator PVPSERVER field to enable all spells."); } }); Util.setReason("Make heated food never decay if cooked by a royal cook."); CtClass ctTempStates = classPool.get("com.wurmonline.server.items.TempStates"); replace = "$_ = $proceed($$);" + "if(chefMade){" + " $0.setName(\"royal \"+$0.getName());" + " $0.setHasNoDecay(true);" + "}"; Util.instrumentDeclared(thisClass, ctTempStates, "checkForChange", "setName", replace); CtClass[] params13 = { ctItem }; String desc13 = Descriptor.ofMethod(ctVehicle, params13); Util.setReason("Buffing caravels to be less based on wind"); CtClass ctVehicles = classPool.get("com.wurmonline.server.behaviours.Vehicles"); replace = "$_ = $proceed($1, $2);" + "" + MiscChanges.class.getName() + ".buffBoat($2, $1);"; Util.instrumentDescribed(thisClass, ctVehicles, "createVehicle", desc13, "setSettingsForVehicle", replace); Util.setReason("Stop royal food decay."); // Item parent, int parentTemp, boolean insideStructure, boolean deeded, boolean saveLastMaintained, boolean inMagicContainer, boolean inTrashbin CtClass[] params11 = { ctItem, CtClass.intType, CtClass.booleanType, CtClass.booleanType, CtClass.booleanType, CtClass.booleanType, CtClass.booleanType }; String desc11 = Descriptor.ofMethod(CtClass.booleanType, params11); replace = "if($0.isFood() && $0.hasNoDecay()){" + " $_ = false;" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDescribed(thisClass, ctItem, "poll", desc11, "setDamage", replace); Util.setReason("Allow mayors to command abandoned vehicles off their deed."); replace = "if("+MiscChanges.class.getName()+".checkMayorCommand($0, $1)){" + " return true;" + "}"; Util.insertBeforeDeclared(thisClass, ctItem, "mayCommand", replace); Util.setReason("Modify timed affinity timer."); CtClass ctAffinitiesTimed = classPool.get("com.wurmonline.server.skills.AffinitiesTimed"); replace = "$_ = "+MiscChanges.class.getName()+".getFoodOpulenceBonus($0);"; Util.instrumentDeclared(thisClass, ctAffinitiesTimed, "addTimedAffinityFromBonus", "getFoodComplexity", replace); Util.setReason("Food affinity timer normalization."); replace = "long time = "+WurmCalendar.class.getName()+".getCurrentTime();" + "if($0.getExpires($1) == null){" + " $_ = Long.valueOf(time);" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctAffinitiesTimed, "add", "getExpires", replace); Util.setReason("Make weaponsmithing oils actually help with weaponsmithing"); replace = "if($1 == 1016){" + "return $0.getSpellEffectPower((byte)77);" + "}"; Util.insertBeforeDeclared(thisClass, ctItem, "getSkillSpellImprovement", replace); Util.setReason("Make bed QL affect sleep bonus timer."); CtClass ctPlayerInfo = classPool.get("com.wurmonline.server.players.PlayerInfo"); replace = "long secs2 = "+MiscChanges.class.getName()+".getBedBonus(secs, this.bed);" + "$_ = $proceed((int)(this.sleep + secs2));"; Util.instrumentDeclared(thisClass, ctPlayerInfo, "calculateSleep", "setSleep", replace); Util.setReason("Fix intrateleport block bug."); replace = "if($6.contains(\"blocked\")){" + " logger.info(\"Detected a blocked movement, resetting position back to old.\");" + " $1 = $0.getMovementScheme().xOld;" + " $2 = $0.getMovementScheme().yOld;" + "}"; Util.insertBeforeDeclared(thisClass, ctCreature, "intraTeleport", replace); Util.setReason("Allow royal smith to improve smithing items faster."); replace = "if("+MiscChanges.class.getName()+".royalSmithImprove($1, improve)){" + " $_ = $proceed($$) * 0.9f;" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctMethodsItems, "improveItem", "getImproveActionTime", replace); Util.setReason("Allow royal smith to improve smithing items faster."); Util.instrumentDeclared(thisClass, ctMethodsItems, "polishItem", "getImproveActionTime", replace); Util.setReason("Allow royal smith to improve smithing items faster. Also make tempering use water enchants."); replace = "if("+MiscChanges.class.getName()+".royalSmithImprove($1, improve)){" + " $_ = $proceed($1, target) * 0.9f;" + "}else{" + " $_ = $proceed($1, target);" + "}"; Util.instrumentDeclared(thisClass, ctMethodsItems, "temper", "getImproveActionTime", replace); // Fix for body strength not working properly when mounted. (Bdew) ctCreature.getMethod("getTraitMovePercent", "(Z)F").instrument(new ExprEditor() { private boolean first = true; @Override public void edit(MethodCall m) throws CannotCompileException { if (m.getMethodName().equals("getStrengthSkill")) { if (first) m.replace("wmod = wmod * 3D; $_ = $proceed() * (this.isUnicorn()?3D:2D);"); else m.replace("$_ = $proceed() * (this.isUnicorn()?3D:2D);"); first = false; } } }); Util.setReason("Modify food fill percent."); CtClass[] params12 = { ctAction, ctCreature, ctItem, CtClass.floatType }; String desc12 = Descriptor.ofMethod(CtClass.booleanType, params12); replace = "$_ = $proceed($1, $2, $3, $4, "+MiscChanges.class.getName()+".getNewFoodFill(qlevel));"; Util.instrumentDescribed(thisClass, ctMethodsItems, "eat", desc12, "modifyHunger", replace); // Fix for butchering not giving skill gain when butchering too many items ctMethodsItems.getDeclaredMethod("filet").instrument(new ExprEditor() { private boolean first = true; @Override public void edit(MethodCall m) throws CannotCompileException { if (m.getMethodName().equals("skillCheck")) { if (first) { first = false; }else { m.replace("$_ = $proceed($1, $2, $3, false, $5);"); logger.info("Replaced filet skill check to ensure butchering skill is always gained."); } } } }); // How to add a skill! /*CtClass ctSkillSystem = classPool.get("com.wurmonline.server.skills.SkillSystem"); CtConstructor ctSkillSystemConstructor = ctSkillSystem.getClassInitializer(); ctSkillSystemConstructor.insertAfter("com.wurmonline.server.skills.SkillSystem.addSkillTemplate(new "+SkillTemplate.class.getName()+"(10096, \"Battle Yoyos\", 4000.0f, new int[]{1022}, 1209600000l, (short) 4, true, true));");*/ Util.setReason("Hook for rare material usage in improvement."); replace = "if("+MiscChanges.class.getName()+".rollRarityImprove($0, usedWeight)){" + " rarity = source.getRarity();" + "}" + "$_ = $proceed($$);"; Util.instrumentDeclared(thisClass, ctMethodsItems, "improveItem", "setWeight", replace); Util.setReason("Bad luck protection on rarity windows."); replace = "if($1 == 3600){" + " $_ = "+MiscChanges.class.getName()+".getRarityWindowChance(this.getWurmId());" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDeclared(thisClass, ctPlayer, "poll", "nextInt", replace); CtClass ctShop = classPool.get("com.wurmonline.server.items.Trade"); Util.setReason("Restock certain items after every trade with a trader"); replace = "$_ = $proceed($$);" + MiscChanges.class.getName() + ".restockTrader($1);"; Util.instrumentDeclared(thisClass, ctShop, "makeTrade", "getShop", replace); //Util.setReason("Allow preists of vynora to gain 20% boat speed above 60 faith."); //replace = "$_ = $proceed($1, $2 * " + MiscChanges.class.getName() + ".boatSpeedBonus(Players.getInstance().getPlayer(this.pilotId)));"; ctVehicle.getDeclaredMethod("calculateNewBoatSpeed").instrument(new ExprEditor() { private boolean first = true; @Override public void edit(MethodCall m) throws CannotCompileException { if(m.getMethodName().equals("min")) { if(first) { first = false; } else { m.replace("$_ = $proceed($1, $2 * " + MiscChanges.class.getName() + ".boatSpeedBonus(this));"); logger.info("Allowing preists of vynora to gain 20% boat speed above 60 faith."); } } } }); CtClass ctSimpleCreationEntry = classPool.get("com.wurmonline.server.items.SimpleCreationEntry"); ctSimpleCreationEntry.getDeclaredMethod("run").instrument(new ExprEditor() { private boolean first = true; @Override public void edit(MethodCall m) throws CannotCompileException { if (m.getMethodName().equals("getRarity")) { if (first) { m.replace("byte newRarity = "+MiscChanges.class.getName()+".getNewCreationRarity(this, source, target, template);" + "if(newRarity > 0){" + " act.setRarity(newRarity);" + "}" + "$_ = $proceed($$);"); logger.info("Replaced getRarity in SimpleCreationEntry to allow functional rare creations."); first = false; } } } }); Util.setReason("Make armour title benefits always occur."); replace = "$_ = improve.getNumber();"; Util.instrumentDeclared(thisClass, ctMethodsItems, "improveItem", "getSkillId", replace); Util.instrumentDeclared(thisClass, ctMethodsItems, "polishItem", "getSkillId", replace); Util.setReason("Make it so sorceries can be used anywhere with a flat 3x3 altar."); CtClass ctAbilities = classPool.get("com.wurmonline.server.players.Abilities"); replace = "$_ = 1;"; Util.instrumentDeclared(thisClass, ctAbilities, "isInProperLocation", "getTemplateId", replace); Util.setReason("Make the key of the heavens only usable on PvE"); replace = "if($1.getTemplateId() == 794 && com.wurmonline.server.Servers.localServer.PVPSERVER){" + " $2.getCommunicator().sendNormalServerMessage(\"The \"+$1.getName()+\" may not be used on Arena.\");" + " return false;" + "}"; Util.insertBeforeDeclared(thisClass, ctAbilities, "isInProperLocation", replace); Util.setReason("Make drinks less filling."); CtClass[] params14 = { ctAction, ctCreature, ctItem, CtClass.floatType }; String desc14 = Descriptor.ofMethod(CtClass.booleanType, params14); replace = "if(template != 128){" + " $_ = $proceed($1, $2, $3*5);" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDescribed(thisClass, ctMethodsItems, "drink", desc14, "sendActionControl", replace); replace = "if(template != 128){" + " $_ = $proceed($1/5, $2, $3, $4, $5);" + "}else{" + " $_ = $proceed($$);" + "}"; Util.instrumentDescribed(thisClass, ctMethodsItems, "drink", desc14, "modifyThirst", replace); Util.setReason("Disable Gem Augmentation skill from converting."); CtClass ctMethodsReligion = classPool.get("com.wurmonline.server.behaviours.MethodsReligion"); replace = "$_ = $proceed($1, $2, true, $4);"; Util.instrumentDeclared(thisClass, ctMethodsReligion, "listen", "skillCheck", replace); Util.setReason("Disable GM commands from displaying in /help unless the player is a GM."); CtClass ctServerTweaksHandler = classPool.get("com.wurmonline.server.ServerTweaksHandler"); replace = "if($1.getPower() < 1){" + " return;" + "}"; Util.insertBeforeDeclared(thisClass, ctServerTweaksHandler, "sendHelp", replace); Util.setReason("Make damage less likely to interrupt actions during combat."); replace = "$1 = $1/2;"; Util.insertBeforeDeclared(thisClass, ctCreature, "maybeInterruptAction", replace); Util.setReason("Fix mission null pointer exception."); CtClass ctEpicServerStatus = classPool.get("com.wurmonline.server.epic.EpicServerStatus"); replace = "if(itemplates.size() < 1){" + " com.wurmonline.server.epic.EpicServerStatus.setupMissionItemTemplates();" + "}"; Util.insertBeforeDeclared(thisClass, ctEpicServerStatus, "getRandomItemTemplateUsed", replace); Util.setReason("Fix bug causing high cast spells to reduce power."); CtClass ctSpellEffect = classPool.get("com.wurmonline.server.spells.SpellEffect"); replace = "{" + " final float mod = 5.0f * (1.0f - java.lang.Math.min($0.getPower(), 100f) / 100.0f);" + " $0.setPower(mod + $1);" + "}"; Util.setBodyDeclared(thisClass, ctSpellEffect, "improvePower", replace); Util.setReason("Disable smelting pots from being used."); CtClass ctItemBehaviour = classPool.get("com.wurmonline.server.behaviours.ItemBehaviour"); CtClass[] params15 = { ctAction, ctCreature, ctItem, ctItem, CtClass.shortType, CtClass.floatType }; String desc15 = Descriptor.ofMethod(CtClass.booleanType, params15); replace = "if($5 == 519){" + " $2.getCommunicator().sendNormalServerMessage(\"Smelting is disabled.\");" + " return true;" + "}"; Util.insertBeforeDescribed(thisClass, ctItemBehaviour, "action", desc15, replace); } catch (CannotCompileException | NotFoundException | IllegalArgumentException | ClassCastException e) { throw new HookException(e); } } public static void logMessage(String message){ logger.info(message); } }