Index: C:/workspace/c4/java/net/sf/l2j/gameserver/RecipeController.java =================================================================== --- C:/workspace/c4/java/net/sf/l2j/gameserver/RecipeController.java (revision 1680) +++ C:/workspace/c4/java/net/sf/l2j/gameserver/RecipeController.java (working copy) @@ -35,6 +35,7 @@ import net.sf.l2j.Config; import net.sf.l2j.gameserver.ClientScheduler; import net.sf.l2j.gameserver.GameTimeController; +import net.sf.l2j.gameserver.idfactory.IdFactory; import net.sf.l2j.gameserver.lib.Rnd; import net.sf.l2j.gameserver.model.Inventory; import net.sf.l2j.gameserver.model.L2ItemInstance; @@ -42,6 +43,7 @@ import net.sf.l2j.gameserver.model.L2RecipeInstance; import net.sf.l2j.gameserver.model.L2RecipeList; import net.sf.l2j.gameserver.model.L2Skill; +import net.sf.l2j.gameserver.model.L2World; import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance; import net.sf.l2j.gameserver.serverpackets.ActionFailed; import net.sf.l2j.gameserver.serverpackets.ItemList; @@ -159,7 +161,8 @@ public synchronized void requestMakeItemAbort(L2PcInstance player) { - activeMakers.remove(player); + if(activeMakers!=null) activeMakers.remove(player); + player.isInCraftMode(false); } public synchronized void requestManufactureItem(L2PcInstance manufacturer, int recipeListId, @@ -170,21 +173,23 @@ if (recipeList == null) return; manufacturer.isInCraftMode(true); - if (!Config.ALT_GAME_CREATION) + RecipeItemMaker maker; + + if (Config.ALT_GAME_CREATION && (maker = activeMakers.get(manufacturer)) != null) // check if busy { - new RecipeItemMaker(manufacturer, recipeList, player); + player.sendMessage("Manufacturer is busy, please try later."); + manufacturer.isInCraftMode(false); + return; } - else + + maker = new RecipeItemMaker(manufacturer, recipeList, player); + if(maker.isValid) { - RecipeItemMaker maker = activeMakers.get(manufacturer); - if (maker != null) + if (Config.ALT_GAME_CREATION) { - player.sendMessage("Manufacturer is busy creating."); - manufacturer.isInCraftMode(false); - return; - } - maker = new RecipeItemMaker(manufacturer, recipeList, player); - if (maker.isValid) activeMakers.put(manufacturer, maker); + activeMakers.put(manufacturer, maker); + ClientScheduler.scheduleEvent(maker, 100); + } else maker.run(); } } @@ -192,28 +197,34 @@ { L2RecipeList recipeList = getValidRecipeList(player, recipeListId); - if (recipeList == null) return; - + if (recipeList == null) + { + _log.warning("recipelist == null"); + return; + } + player.isInCraftMode(true); - if (!Config.ALT_GAME_CREATION) + RecipeItemMaker maker; + + if (Config.ALT_GAME_CREATION && ((maker = activeMakers.get(player)) != null)) // already have something cooking (possible in alt mode only) { - new RecipeItemMaker(player, recipeList, player); + SystemMessage sm = new SystemMessage(614); + sm.addString("You are busy creating "); + sm.addItemName(recipeList.getItemId()); + player.sendPacket(sm); + player.isInCraftMode(false); + return; } - else + + maker = new RecipeItemMaker(player, recipeList, player); + if(maker.isValid) { - RecipeItemMaker maker = activeMakers.get(player); - if (maker != null) + if (Config.ALT_GAME_CREATION) { - SystemMessage sm = new SystemMessage(614); - sm.addString("You are busy creating "); - sm.addItemName(recipeList.getItemId()); - player.sendPacket(sm); - player.isInCraftMode(false); - return; - } - maker = new RecipeItemMaker(player, recipeList, player); - if (maker.isValid) activeMakers.put(player, maker); + activeMakers.put(player, maker); + ClientScheduler.scheduleEvent(maker, 100); + } else maker.run(); } } @@ -234,7 +245,7 @@ else if (recipeTypeString.equalsIgnoreCase("common")) isDwarvenRecipe = false; else { //prints a helpfull message - _log.warning("Error parsing recipes.csv, unknow recipe type " + recipeTypeString); + _log.warning("Error parsing recipes.csv, unknown recipe type " + recipeTypeString); return; } @@ -281,118 +292,174 @@ public class RecipeItemMaker implements Runnable { public boolean isValid; - final List items; - int materialsRefPrice; - final L2PcInstance player; + List items = null; + final L2RecipeList recipeList; + final L2PcInstance player; // "crafter" + final L2PcInstance target; // "customer" + final L2Skill skill; + final int skillId; + final int skillLevel; + int creationPasses; int price; - final L2RecipeList recipeList; - final L2PcInstance target; - int timeReady; int delay; - int gameTicks; int totalItems; - int usedItems; - int skillLevel; - + int materialsRefPrice; + double manaRequired; + public RecipeItemMaker(L2PcInstance player, L2RecipeList recipeList, L2PcInstance target) { this.player = player; this.target = target; this.recipeList = recipeList; - this.items = new FastList(); this.isValid = false; + this.skillId = recipeList.isDwarvenRecipe() ? L2Skill.SKILL_CREATE_DWARVEN + : L2Skill.SKILL_CREATE_COMMON; + this.skillLevel = player.getSkillLevel(skillId); + this.skill = player.getKnownSkill(skillId); - skillLevel = recipeList.isDwarvenRecipe() ? player.getSkillLevel(L2Skill.SKILL_CREATE_DWARVEN) - : player.getSkillLevel(L2Skill.SKILL_CREATE_COMMON); + // validate recipe list + if ((recipeList == null) || (recipeList.getRecipes().length == 0)) + { + player.sendMessage("No such recipe"); + player.sendPacket(new ActionFailed()); + abort(false); + return; + } - if (!validateSkillLevel()) return; - if (!validateRecipeList()) return; - if (!validateCustomerCanAfford(target,player)) return; - if (!validateMaterials()) return; - if (!validateMp()) return; + this.manaRequired = this.recipeList.getMpCost(); + + // validate skill level + if (this.recipeList.getLevel() > skillLevel) + { + player.sendMessage("Need skill level " + this.recipeList.getLevel()); + player.sendPacket(new ActionFailed()); + abort(false); + return; + } - isValid = true; - updateTime(); - run(); - } - - private void finishCrafting() - { - player.reduceCurrentMp(recipeList.getMpCost()); - - if (Rnd.get(100) < recipeList.getSuccessRate()) + // check that customer can afford to pay for creation services + if (player != target) { - if (target != player) - { - if (price > 0) + for (L2ManufactureItem temp : player.getCreateList().getList()) + if (temp.getRecipeId() == this.recipeList.getId()) // find recipe for item we want manufactured { - target.reduceAdena("Manufacture", price, player, false); - player.addAdena("Manufacture", price, target, false); + price=temp.getCost(); + if (target.getAdena() < price) // check price + { + target.sendPacket(new SystemMessage(SystemMessage.YOU_NOT_ENOUGH_ADENA)); + abort(false); + return; + } + break; } - updateCurLoad(); - } - - RewardPlayer(); - updateMakeInfo(true); // success } - else + + // make temporary items + if ((items = getItems(false)) == null) return; + + // calculate reference price + for (L2ItemInstance i : items) { - player.sendMessage("Item(s) failed to create"); - updateMakeInfo(false); + materialsRefPrice += i.getReferencePrice() * i.getCount(); + totalItems += i.getCount(); } - activeMakers.remove(player); - player.isInCraftMode(false); + + if(player.getCurrentMp() more time) + delay = (int) (Config.ALT_GAME_CREATION_SPEED * player.getMReuseRate(skill) + * GameTimeController.TICKS_PER_SECOND / Config.RATE_CONSUMABLE_COST) + * GameTimeController.MILLIS_IN_TICK; + if (Config.ALT_GAME_CREATION && !items.isEmpty()) { - player.reduceCurrentMp(recipeList.getMpCost()); // use some mp + player.reduceCurrentMp(manaRequired); // use some mp grabSomeItems(); // grab (equip) some more items with a nice msg to player - MagicSkillUser msk = new MagicSkillUser( - player, - recipeList.isDwarvenRecipe() ? L2Skill.SKILL_CREATE_DWARVEN - : L2Skill.SKILL_CREATE_COMMON, - skillLevel, delay, 0); - + // FIXME: this is not working ... this should be animation of crafting skill ?? or what ? + MagicSkillUser msk = new MagicSkillUser(player, skillId, skillLevel, delay, 0); player.broadcastPacket(msk); player.sendPacket(new SetupGauge(0, delay)); - ClientScheduler.getInstance().scheduleLow(this, 100 + delay); + ClientScheduler.scheduleEvent(this, 100 + delay); } - else finishCrafting(); + else // finish crafting + { + player.reduceCurrentMp(manaRequired); + if (Rnd.get(100) < recipeList.getSuccessRate()) + { + if (target != player) // customer must pay for services + { + if (price > 0) + { + target.transferItem("PayForCrafting", + target.getInventory().getAdenaInstance().getObjectId(), + price, player.getInventory(), null); + } + updateCurLoad(); + } + + if ((items = getItems(true)) != null) // this line actually takes materials from inventory + RewardPlayer(); // and this one immediately puts created item in its place + else updateMakeInfo(false); + } + else + { + player.sendMessage("Item(s) failed to create"); + updateMakeInfo(false); + } + if (Config.ALT_GAME_CREATION) activeMakers.remove(player); + player.isInCraftMode(false); + target.sendPacket(new ItemList(target, false)); + } + // update load and mana bar of craft window updateMakeInfo(true); updateCurMp(); updateCurLoad(); } - private void updateTime() - { - // divided by RATE_CONSUMABLES_COST to remove craft time increase on higher consumable rates (more mats consumed -> more time) - timeReady = gameTicks - + (int) (Config.ALT_GAME_CREATION_SPEED - * player.getMReuseRate(player.getKnownSkill(172)) - * GameTimeController.TICKS_PER_SECOND / Config.RATE_CONSUMABLE_COST); - - delay = (timeReady - gameTicks) * GameTimeController.MILLIS_IN_TICK; - } - private void updateMakeInfo(boolean success) { - if (target == player) target.sendPacket(new RecipeItemMakeInfo(recipeList.getId(), target, success)); + if (target == player) target.sendPacket(new RecipeItemMakeInfo(recipeList.getId(), target, + success)); else target.sendPacket(new RecipeShopItemInfo(player.getObjectId(), recipeList.getId())); } @@ -413,7 +480,7 @@ private void grabSomeItems() { - int numItems = player.getSkillLevel(172); + int numItems = skillLevel; while (numItems > 0 && !items.isEmpty()) { L2ItemInstance item = items.get(0); @@ -423,135 +490,99 @@ item.setCount(item.getCount() - count); if (item.getCount() <= 0) items.remove(0); + else items.set(0, item); numItems -= count; - usedItems += count; SystemMessage sm = new SystemMessage(368); // you equipped ... sm.addNumber(count); sm.addItemName(item.getItemId()); - if (target == null) player.sendPacket(sm); - else target.sendPacket(sm); + // FIXME: should not send "you equipped ... " message to customer so I improvised + if (target == player) player.sendPacket(sm); + else target.sendMessage("Manufacturer " + player.getName() + " used " + count + " " + + item.getItem().getName()); } } - private boolean validateCraftingEnabled() + private boolean validateMp() { - if (!Config.IS_CRAFTING_ENABLED) + if (player.getCurrentMp() < manaRequired) { - target.sendMessage("Crafting is currently disabled."); - player.isInCraftMode(false); - activeMakers.remove(player); - updateMakeInfo(false); - return false; - } - return true; - } - - private boolean validateCustomerCanAfford(L2PcInstance customer, L2PcInstance manufacturer) - { - if(customer==manufacturer) return true; - for (L2ManufactureItem temp : manufacturer.getCreateList().getList()) - if (temp.getRecipeId() == recipeList.getId()) // find recipe for item we want manufactured + // rest (wait for MP) + if (Config.ALT_GAME_CREATION) { - int price = temp.getCost(); - if (customer.getAdena() < price) // check price - { - customer.sendPacket(new SystemMessage(SystemMessage.YOU_NOT_ENOUGH_ADENA)); - manufacturer.isInCraftMode(false); - activeMakers.remove(manufacturer); - return false; - } - break; + player.sendPacket(new SetupGauge(0, delay)); + ClientScheduler.scheduleEvent(this, 100 + delay); + abort(true); // true == can resume } - return true; - } - private boolean validateMp() - { - if (player.getCurrentMp() < recipeList.getMpCost()) - { - target.sendPacket(new SystemMessage(SystemMessage.NOT_ENOUGH_MP)); - player.isInCraftMode(false); - activeMakers.remove(player); - updateMakeInfo(false); + else + // no rest - report no mana + { + target.sendPacket(new SystemMessage(SystemMessage.NOT_ENOUGH_MP)); + abort(false); // false == don't resume + } return false; } return true; } - private boolean validateMaterials() + private List getItems(boolean remove) { L2RecipeInstance[] recipes = recipeList.getRecipes(); - Inventory inv = target.getInventory(); + List materials = new FastList(); for (L2RecipeInstance recipe : recipes) { - if (recipe.getQuantity() != 0) + int quantity = (int) recipe.getQuantity(); + if (recipeList.isConsumable()) + quantity = (int) (quantity * Config.RATE_CONSUMABLE_COST); + + if (quantity > 0) { - L2ItemInstance invItem = inv.getItemByItemId(recipe.getItemId()); - if ((invItem == null) - || ((recipeList.isConsumable() ? (int) (recipe.getQuantity() * Config.RATE_CONSUMABLE_COST) - : recipe.getQuantity()) > invItem.getCount())) + L2ItemInstance item = inv.getItemByItemId(recipe.getItemId()); + int old_quantity = item.getCount(); // remember old quantity + + // now we verify that we have enough materials + if (item.getCount() < quantity) { target.sendMessage("You dont have the right elements for making this item" + ((recipeList.isConsumable() && Config.RATE_CONSUMABLE_COST != 1) ? ".\nDue to server rates you need " + Config.RATE_CONSUMABLE_COST + "x more material than listed in recipe" : "")); - player.isInCraftMode(false); - activeMakers.remove(player); - updateMakeInfo(false); - return false; + abort(false); + return null; } - } - } - for (L2RecipeInstance recipe : recipes) - { - if (recipe.getQuantity() != 0) - { - L2ItemInstance invItem = inv.getItemByItemId(recipe.getItemId()); - invItem = inv.dropItem( - "Manufacture", - invItem.getObjectId(), - recipeList.isConsumable() ? (int) (recipe.getQuantity() * Config.RATE_CONSUMABLE_COST) - : recipe.getQuantity(), target, - player); - items.add(invItem); - materialsRefPrice += invItem.getReferencePrice() * invItem.getCount(); - totalItems += invItem.getCount(); + // make new temporary object, just for counting puroses, real item modification only + // on finishCrafting() so we minimize risk of losing items + + L2ItemInstance temp = new L2ItemInstance(IdFactory.getInstance().getNextId(), + item.getItem().getItemId()); + temp.setCount("TemporaryCraftObject", quantity, target, player); + materials.add(temp); + + if (remove) item.setCount("Manufacture", old_quantity - quantity, target, player); } } - - return true; + return materials; } - private boolean validateSkillLevel() + private void abort(boolean resume) { - if (recipeList.getLevel() > skillLevel) + + if (resume) { - player.sendMessage("Need skill level " + recipeList.getLevel()); - player.sendPacket(new ActionFailed()); - player.isInCraftMode(false); - activeMakers.remove(player); - updateMakeInfo(false); - return false; + // will continue later, run() is scheduled to run in 100+delay miliseconds so no worry + // about blocking other things ... requestMakeItemAbort will interrupt this properly ? } - return true; - } - - private boolean validateRecipeList() - { - if ((recipeList == null) || (recipeList.getRecipes().length == 0)) + else { - player.sendMessage("No such recipe"); + updateMakeInfo(false); + activeMakers.remove(player); player.isInCraftMode(false); - activeMakers.remove(player); - updateMakeInfo(false); - return false; } - return true; } private void RewardPlayer() @@ -561,35 +592,48 @@ L2ItemInstance createdItem = target.getInventory().addItem("Manufacture", itemId, itemCount, target, player); + + // inform customer of earned item SystemMessage sm = new SystemMessage(SystemMessage.EARNED_ITEM); sm.addItemName(itemId); sm.addNumber(itemCount); target.sendPacket(sm); + // inform manufacturer of earned profit + sm = new SystemMessage(SystemMessage.EARNED_ADENA); + sm.addNumber(price); + player.sendPacket(sm); + if (Config.ALT_GAME_CREATION) { int recipeLevel = recipeList.getLevel(); int exp = createdItem.getReferencePrice() * itemCount; // one variation // exp -= materialsRefPrice; - + if (exp < 0) exp = 0; - + // another variation exp /= recipeLevel; - for (int i = skillLevel; i > recipeLevel; i--) exp /= 4; - + for (int i = skillLevel; i > recipeLevel; i--) + exp /= 4; + int sp = exp / 10; - player.addExpAndSp((int) player.calcStat(Stats.EXPSP_RATE, exp * Config.RATE_XP, null, - null), - (int) player.calcStat(Stats.EXPSP_RATE, sp * Config.RATE_SP, null, - null)); - sm = new SystemMessage(SystemMessage.YOU_EARNED_S1_EXP_AND_S2_SP); - sm.addNumber((int) player.calcStat(Stats.EXPSP_RATE, exp * Config.RATE_XP, null, null)); - sm.addNumber((int) player.calcStat(Stats.EXPSP_RATE, sp * Config.RATE_SP, null, null)); - player.sendPacket(sm); + // FIXME: Added multiplication of Creation speed with XP/SP gained ... + // so slower crafting -> more XP, faster crafting -> less XP + // admins: find balance you like ... you can use ALT_GAME_CREATION_RATE_XP/SP to + // compensate XP/SP (default = 1) my goal is to make ALT_GAME_CREATION + // a *bit* slower, a bit more interesting, and give some xp/sp but not too much + // comments on this would be welcome. + + player.addExpAndSp((int) player.calcStat(Stats.EXPSP_RATE, exp * Config.RATE_XP + * Config.ALT_GAME_CREATION_XP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null), + (int) player.calcStat(Stats.EXPSP_RATE, sp * Config.RATE_SP + * Config.ALT_GAME_CREATION_SP_RATE + * Config.ALT_GAME_CREATION_SPEED, null, null)); } + updateMakeInfo(true); // success } } @@ -605,7 +649,5 @@ } return recipeList; }; - } -