From cd5d0e2ad9493d8ca8dac90c0b594375d812d7d7 Mon Sep 17 00:00:00 2001 From: pimittens Date: Sat, 11 Jan 2025 22:31:06 -0800 Subject: [PATCH 1/3] drop rate fixes The probability was very slightly off when calculating drop chances, and there was an error in instances where the maximum number of items dropped was greater than 1. --- src/main/java/server/maps/MapleMap.java | 10 ++++---- src/test/java/tools/NextIntTest.java | 33 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/test/java/tools/NextIntTest.java diff --git a/src/main/java/server/maps/MapleMap.java b/src/main/java/server/maps/MapleMap.java index ceadc01aff1..2766be48c0c 100644 --- a/src/main/java/server/maps/MapleMap.java +++ b/src/main/java/server/maps/MapleMap.java @@ -668,14 +668,14 @@ private byte dropItemsFromMonsterOnMap(List dropEntry, Point p float cardRate = chr.getCardRate(de.itemId); int dropChance = (int) Math.min((float) de.chance * chRate * cardRate, Integer.MAX_VALUE); - if (Randomizer.nextInt(999999) < dropChance) { + if (Randomizer.nextInt(1000000) < dropChance) { if (droptype == 3) { pos.x = mobpos + ((d % 2 == 0) ? (40 * ((d + 1) / 2)) : -(40 * (d / 2))); } else { pos.x = mobpos + ((d % 2 == 0) ? (25 * ((d + 1) / 2)) : -(25 * (d / 2))); } if (de.itemId == 0) { // meso - int mesos = Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum; + int mesos = Randomizer.nextInt(de.Maximum - de.Minimum + 1) + de.Minimum; if (mesos > 0) { if (chr.getBuffedValue(BuffStat.MESOUP) != null) { @@ -692,7 +692,7 @@ private byte dropItemsFromMonsterOnMap(List dropEntry, Point p if (ItemConstants.getInventoryType(de.itemId) == InventoryType.EQUIP) { idrop = ii.randomizeStats((Equip) ii.getEquipById(de.itemId)); } else { - idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1)); + idrop = new Item(de.itemId, (short) 0, (short) (Randomizer.nextInt(de.Maximum - de.Minimum + 1) + de.Minimum)); } spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid); } @@ -710,7 +710,7 @@ private byte dropGlobalItemsFromMonsterOnMap(List global ItemInformationProvider ii = ItemInformationProvider.getInstance(); for (final MonsterGlobalDropEntry de : globalEntry) { - if (Randomizer.nextInt(999999) < de.chance) { + if (Randomizer.nextInt(1000000) < de.chance) { if (droptype == 3) { pos.x = mobpos + (d % 2 == 0 ? (40 * (d + 1) / 2) : -(40 * (d / 2))); } else { @@ -720,7 +720,7 @@ private byte dropGlobalItemsFromMonsterOnMap(List global if (ItemConstants.getInventoryType(de.itemId) == InventoryType.EQUIP) { idrop = ii.randomizeStats((Equip) ii.getEquipById(de.itemId)); } else { - idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1)); + idrop = new Item(de.itemId, (short) 0, (short) (Randomizer.nextInt(de.Maximum - de.Minimum + 1) + de.Minimum)); } spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid); d++; diff --git a/src/test/java/tools/NextIntTest.java b/src/test/java/tools/NextIntTest.java new file mode 100644 index 00000000000..628184e6414 --- /dev/null +++ b/src/test/java/tools/NextIntTest.java @@ -0,0 +1,33 @@ +package tools; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NextIntTest { + + @Test + void dropQuantityShouldIncludeEntireRange() { + Map rands; + final int rounds = 100_000; + for (int min = 1; min < 100; min++) { + for (int max = min; max < 100; max++) { + rands = new HashMap<>(); + for (int i = 0; i < rounds; i++) { + int randomValue = Randomizer.nextInt(max - min + 1) + min; + rands.compute(randomValue, (k, v) -> v == null ? 0 : v + 1); + } + + assertFalse(rands.containsKey(min - 1)); + for (int i = min; i <= max; i++) { + assertTrue(rands.containsKey(i)); + } + assertFalse(rands.containsKey(max + 1)); + } + } + } +} From b9f47ff2d595aba41048b9ac936a14fd5cd98673 Mon Sep 17 00:00:00 2001 From: pimittens Date: Sat, 18 Jan 2025 19:41:39 -0800 Subject: [PATCH 2/3] clean slate and legendary spirit fix Clean slate scrolls could not be used on equips with 0 slots remaining. Additionally, using a scroll with legendary spirit on an equip with 0 slots remaining would freeze the legendary spirit window. This fixes both issues by adding an isCleanSlate check and sending a different packet in the legendary spirit case (the player will still need to dispose after). --- .../net/server/channel/handlers/ScrollHandler.java | 4 ++-- src/main/java/tools/PacketCreator.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/ScrollHandler.java b/src/main/java/net/server/channel/handlers/ScrollHandler.java index 4d6f3b12c94..4ffb0ed5f60 100644 --- a/src/main/java/net/server/channel/handlers/ScrollHandler.java +++ b/src/main/java/net/server/channel/handlers/ScrollHandler.java @@ -79,7 +79,7 @@ public final void handlePacket(InPacket p, Client c) { if (ItemConstants.isCleanSlate(scroll.getItemId()) && !ii.canUseCleanSlate(toScroll)) { announceCannotScroll(c, legendarySpirit); return; - } else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) { + } else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && !ItemConstants.isCleanSlate(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) { announceCannotScroll(c, legendarySpirit); // thanks onechord for noticing zero upgrade slots freezing Legendary Scroll UI return; } @@ -180,7 +180,7 @@ public final void handlePacket(InPacket p, Client c) { private static void announceCannotScroll(Client c, boolean legendarySpirit) { if (legendarySpirit) { - c.sendPacket(PacketCreator.getScrollEffect(c.getPlayer().getId(), Equip.ScrollResult.FAIL, false, false)); + c.sendPacket(PacketCreator.legendarySpiritCannotScroll(c.getPlayer().getId())); } else { c.sendPacket(PacketCreator.getInventoryFull()); } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index a6cf3cf6eb8..b6165b11d01 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -2504,6 +2504,16 @@ public static Packet getScrollEffect(int chr, ScrollResult scrollSuccess, boolea return p; } + public static Packet legendarySpiritCannotScroll(int chr) { // suggested by teto + OutPacket p = OutPacket.create(SendOpcode.SHOW_SCROLL_EFFECT); + p.writeInt(chr); + p.writeByte(-1); // CUIEnchantDlg::SetResult => CUtilDlg::Notice("You cannot use a Scroll with this item."); + p.writeBool(false); + p.writeBool(true); // bEnchantSkill + p.writeBool(false); + return p; + } + public static Packet removePlayerFromMap(int chrId) { OutPacket p = OutPacket.create(SendOpcode.REMOVE_PLAYER_FROM_MAP); p.writeInt(chrId); From e7cb86be9ba17fdcb67fca289783176aaae85255 Mon Sep 17 00:00:00 2001 From: pimittens Date: Sat, 18 Jan 2025 19:51:54 -0800 Subject: [PATCH 3/3] Revert "clean slate and legendary spirit fix" This reverts commit b9f47ff2d595aba41048b9ac936a14fd5cd98673. --- .../net/server/channel/handlers/ScrollHandler.java | 4 ++-- src/main/java/tools/PacketCreator.java | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/ScrollHandler.java b/src/main/java/net/server/channel/handlers/ScrollHandler.java index 4ffb0ed5f60..4d6f3b12c94 100644 --- a/src/main/java/net/server/channel/handlers/ScrollHandler.java +++ b/src/main/java/net/server/channel/handlers/ScrollHandler.java @@ -79,7 +79,7 @@ public final void handlePacket(InPacket p, Client c) { if (ItemConstants.isCleanSlate(scroll.getItemId()) && !ii.canUseCleanSlate(toScroll)) { announceCannotScroll(c, legendarySpirit); return; - } else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && !ItemConstants.isCleanSlate(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) { + } else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) { announceCannotScroll(c, legendarySpirit); // thanks onechord for noticing zero upgrade slots freezing Legendary Scroll UI return; } @@ -180,7 +180,7 @@ public final void handlePacket(InPacket p, Client c) { private static void announceCannotScroll(Client c, boolean legendarySpirit) { if (legendarySpirit) { - c.sendPacket(PacketCreator.legendarySpiritCannotScroll(c.getPlayer().getId())); + c.sendPacket(PacketCreator.getScrollEffect(c.getPlayer().getId(), Equip.ScrollResult.FAIL, false, false)); } else { c.sendPacket(PacketCreator.getInventoryFull()); } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index b6165b11d01..a6cf3cf6eb8 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -2504,16 +2504,6 @@ public static Packet getScrollEffect(int chr, ScrollResult scrollSuccess, boolea return p; } - public static Packet legendarySpiritCannotScroll(int chr) { // suggested by teto - OutPacket p = OutPacket.create(SendOpcode.SHOW_SCROLL_EFFECT); - p.writeInt(chr); - p.writeByte(-1); // CUIEnchantDlg::SetResult => CUtilDlg::Notice("You cannot use a Scroll with this item."); - p.writeBool(false); - p.writeBool(true); // bEnchantSkill - p.writeBool(false); - return p; - } - public static Packet removePlayerFromMap(int chrId) { OutPacket p = OutPacket.create(SendOpcode.REMOVE_PLAYER_FROM_MAP); p.writeInt(chrId);