Trade results + Map-Ownership & Fishing + P. EXP distribution rework
Fixed an issue where players could lose priority over recently dropped items after switching maps. Adjusted EXP bonus buffs to also cover party bonus gains. Fixed items taken back from merchants not properly checking for stacking opportunities in player inventory. Fixed some merchant references in visitors' player object not being properly cleared when owner closes shop. Adjusted merchants to automatically close as soon as the merchant owner finishes maintenance process with it having no items in store. Added trade result opcodes. Trade results now should work almost as intended originally. Implemented server-side check for portal distance when deploying player shops and merchants. Implemented server-side check for whether local or remote IP is being used when logging in a local/remote server (this should mitigate a few of the issues people may find when trying to log in game world). Implemented commands designed for management of opened IO sessions. Fixed chalkboard not showing up for owner player when changing maps. Added "time left" functionality for merchant owners managing the opened store. Fixed skillbooks not showing properly for other players in the map. Fixed commands using lowercased-version of content inputted by player. Implemented the Fredrick expected fee on using the Store Bank service. Implemented "exclusive invitation management" in the system. Inviters are notified the invited players are already managing an invite, should it be visually "in-progress" for that one. Implemented "map ownership". Non-map owners are unable to farm in an area if they are not party members with the owner or until the ownership rescinds. Adjusted inventory sort feature, now sorting projectile items in such a fashion that commonly stronger versions comes before the basic ones. Added a visual effect that shows up when obtaining Aran skills. Revised party EXP gain system. Party bonuses now accounts a fraction of the accumulated EXP gained by members when defeating a mob, and raw EXP gained by a player is kept the same regardless of him/her being in a party or not (thus a bonus being REALLY a bonus). Implemented a custom fishing system in the source, on which during "seasonal" times (that gets arbitrarily defined by both day-of-year and time-of-day) fishes are more likely to be hooked. Such likelihood also improved depending on the amount of mesos spent as lure.
This commit is contained in:
@@ -28,6 +28,7 @@ public class FilePrinter {
|
||||
PACKET_LOG = "game/Log.txt",
|
||||
CASHITEM_BOUGHT = "interactions/CashLog.txt",
|
||||
EXCEPTION = "game/Exceptions.txt",
|
||||
LOGIN_EXCEPTION = "game/LoginExceptions.txt",
|
||||
TRADE_EXCEPTION = "game/TradeExceptions.txt",
|
||||
SQL_EXCEPTION = "game/SqlExceptions.txt",
|
||||
PACKET_HANDLER = "game/packethandler/",
|
||||
|
||||
@@ -1065,7 +1065,7 @@ public class MaplePacketCreator {
|
||||
mplew.writeShort(chr.getHp());
|
||||
mplew.writeBool(false);
|
||||
mplew.writeLong(getTime(Server.getInstance().getCurrentTime()));
|
||||
mplew.skip(14);
|
||||
mplew.skip(18);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
@@ -1082,7 +1082,7 @@ public class MaplePacketCreator {
|
||||
mplew.writeInt(spawnPosition.x); // spawn position placement thanks to Arnah (Vertisy)
|
||||
mplew.writeInt(spawnPosition.y);
|
||||
mplew.writeLong(getTime(Server.getInstance().getCurrentTime()));
|
||||
mplew.skip(14);
|
||||
mplew.skip(18);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
@@ -3175,7 +3175,7 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] getTradeInvite(MapleCharacter c) {
|
||||
public static byte[] tradeInvite(MapleCharacter c) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
|
||||
mplew.write(PlayerInteractionHandler.Action.INVITE.getCode());
|
||||
@@ -3309,21 +3309,22 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] getTradeCompletion(byte number) {
|
||||
/**
|
||||
* Possible values for <code>operation</code>:<br> 2: Trade cancelled by the
|
||||
* other character<br> 7: Trade successful<br> 8: Trade unsuccessful<br>
|
||||
* 9: Cannot carry more one-of-a-kind items<br> 12: Cannot trade on different maps<br>
|
||||
* 13: Cannot trade, game files damaged<br>
|
||||
*
|
||||
* @param number
|
||||
* @param operation
|
||||
* @return
|
||||
*/
|
||||
public static byte[] getTradeResult(byte number, byte operation) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5);
|
||||
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
|
||||
mplew.write(PlayerInteractionHandler.Action.EXIT.getCode());
|
||||
mplew.write(number);
|
||||
mplew.write(6);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] getTradeCancel(byte number) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5);
|
||||
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
|
||||
mplew.write(PlayerInteractionHandler.Action.EXIT.getCode());
|
||||
mplew.write(number);
|
||||
mplew.write(2);
|
||||
mplew.write(operation);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
@@ -3783,11 +3784,9 @@ public class MaplePacketCreator {
|
||||
* party. 13: You have yet to join a party.
|
||||
* 16: Already have joined a party. 17: The party you're trying to join is
|
||||
* already in full capacity. 19: Unable to find the requested character in
|
||||
* this channel. 21: Player is blocking any party invitations. 22: Player
|
||||
* is taking care of another invitation. 23: Player denied request.
|
||||
* 25: Cannot kick another user in this map. 28/29: Leadership can only be
|
||||
* given to a party member in the vicinity. 30: Change leadership only on
|
||||
* same channel.
|
||||
* this channel. 25: Cannot kick another user in this map. 28/29: Leadership
|
||||
* can only be given to a party member in the vicinity. 30: Change leadership
|
||||
* only on same channel.
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
@@ -3800,7 +3799,8 @@ public class MaplePacketCreator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 23: 'Char' have denied request to the party.
|
||||
* 21: Player is blocking any party invitations, 22: Player is taking care of
|
||||
* another invitation, 23: Player have denied request to the party.
|
||||
*
|
||||
* @param message
|
||||
* @param charname
|
||||
@@ -4372,25 +4372,43 @@ public class MaplePacketCreator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Char' has denied your guild invitation.
|
||||
* Gets a Heracle/guild message packet.
|
||||
*
|
||||
* @param charname
|
||||
* @return
|
||||
* Possible values for <code>code</code>:<br> 28: guild name already in use<br>
|
||||
* 31: problem in locating players during agreement<br> 33/40: already joined a guild<br>
|
||||
* 35: Cannot make guild<br> 36: problem in player agreement<br> 38: problem during forming guild<br>
|
||||
* 41: max number of players in joining guild<br> 42: character can't be found this channel<br>
|
||||
* 45/48: character not in guild<br> 52: problem in disbanding guild<br> 56: admin cannot make guild<br>
|
||||
* 57: problem in increasing guild size<br>
|
||||
*
|
||||
*
|
||||
* @param code The response code.
|
||||
* @return The guild message packet.
|
||||
*/
|
||||
public static byte[] denyGuildInvitation(String charname) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue());
|
||||
mplew.write(0x37);
|
||||
mplew.writeMapleAsciiString(charname);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] genericGuildMessage(byte code) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue());
|
||||
mplew.write(code);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a guild message packet appended with target name.
|
||||
*
|
||||
* 53: player not accepting guild invites<br>
|
||||
* 54: player already managing an invite<br> 55: player denied an invite<br>
|
||||
*
|
||||
* @param code The response code.
|
||||
* @param targetName The initial player target of the invitation.
|
||||
* @return The guild message packet.
|
||||
*/
|
||||
public static byte[] responseGuildMessage(byte code, String targetName) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue());
|
||||
mplew.write(code);
|
||||
mplew.writeMapleAsciiString(targetName);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] newGuildMember(MapleGuildCharacter mgc) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
@@ -4937,7 +4955,7 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] skillBookSuccess(MapleCharacter chr, int skillid, int maxlevel, boolean canuse, boolean success) {
|
||||
public static byte[] skillBookResult(MapleCharacter chr, int skillid, int maxlevel, boolean canuse, boolean success) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.SKILL_LEARN_ITEM_RESULT.getValue());
|
||||
mplew.writeInt(chr.getId());
|
||||
@@ -5378,7 +5396,7 @@ public class MaplePacketCreator {
|
||||
mplew.writeInt(9030000); // Fredrick
|
||||
mplew.writeInt(32272); //id
|
||||
mplew.skip(5);
|
||||
mplew.writeInt(chr.getMerchantMeso());
|
||||
mplew.writeInt(chr.getMerchantNetMeso());
|
||||
mplew.write(0);
|
||||
try {
|
||||
List<Pair<Item, MapleInventoryType>> items = ItemFactory.MERCHANT.loadItems(chr.getId(), false);
|
||||
@@ -5578,7 +5596,8 @@ public class MaplePacketCreator {
|
||||
}
|
||||
mplew.writeMapleAsciiString(hm.getOwner());
|
||||
if (hm.isOwner(chr)) {
|
||||
mplew.writeInt(hm.getTimeLeft());
|
||||
mplew.writeShort(0);
|
||||
mplew.writeShort(hm.getTimeOpen());
|
||||
mplew.write(firstTime ? 1 : 0);
|
||||
List<MapleHiredMerchant.SoldItem> sold = hm.getSold();
|
||||
mplew.write(sold.size());
|
||||
@@ -6348,6 +6367,16 @@ public class MaplePacketCreator {
|
||||
mplew.writeInt(1);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] showForeignInfo(int cid, String path) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue());
|
||||
mplew.writeInt(cid);
|
||||
mplew.write(0x17);
|
||||
mplew.writeMapleAsciiString(path);
|
||||
mplew.writeInt(1);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a UI utility. 0x01 - Equipment Inventory. 0x02 - Stat Window. 0x03
|
||||
@@ -6875,7 +6904,7 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] sendAllianceInvitation(int allianceid, MapleCharacter chr) {
|
||||
public static byte[] allianceInvite(int allianceid, MapleCharacter chr) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.ALLIANCE_OPERATION.getValue());
|
||||
mplew.write(0x03);
|
||||
|
||||
35
src/tools/exceptions/EventInstanceInProgressException.java
Normal file
35
src/tools/exceptions/EventInstanceInProgressException.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of the HeavenMS MapleStory Server
|
||||
Copyleft (L) 2016 - 2018 RonanLana
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation version 3 as published by
|
||||
the Free Software Foundation. You may not use, modify or distribute
|
||||
this program under any other version of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package tools.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ronan
|
||||
*/
|
||||
public class EventInstanceInProgressException extends Exception {
|
||||
|
||||
public static String EIIP_KEY = "Event instance ";
|
||||
|
||||
public EventInstanceInProgressException(String eventName, String eventInstance) {
|
||||
super(EIIP_KEY + "already in progress - " + eventName + ", EM: " + eventInstance);
|
||||
}
|
||||
|
||||
}
|
||||
199
src/tools/packets/Fishing.java
Normal file
199
src/tools/packets/Fishing.java
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
This file is part of the HeavenMS MapleStory Server
|
||||
Copyleft (L) 2016 - 2018 RonanLana
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation version 3 as published by
|
||||
the Free Software Foundation. You may not use, modify or distribute
|
||||
this program under any other version of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package tools.packets;
|
||||
|
||||
import client.MapleCharacter;
|
||||
import constants.GameConstants;
|
||||
import constants.ItemConstants;
|
||||
import constants.ServerConstants;
|
||||
import server.MapleItemInformationProvider;
|
||||
import tools.MaplePacketCreator;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author FateJiki (RaGeZONE)
|
||||
* @author Ronan - timing pattern
|
||||
*/
|
||||
public class Fishing {
|
||||
|
||||
private static double getFishingLikelihood(int x) {
|
||||
return 50.0 + 7.0 * (7.0 * Math.sin(x)) * (Math.cos(Math.pow(x, 0.777)));
|
||||
}
|
||||
|
||||
public static double[] fetchFishingLikelihood() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
|
||||
|
||||
int hours = calendar.get(Calendar.HOUR);
|
||||
int minutes = calendar.get(Calendar.MINUTE);
|
||||
int seconds = calendar.get(Calendar.SECOND);
|
||||
|
||||
double yearLikelihood = getFishingLikelihood(dayOfYear);
|
||||
double timeLikelihood = getFishingLikelihood(hours + minutes + seconds);
|
||||
|
||||
return new double[]{yearLikelihood, timeLikelihood};
|
||||
}
|
||||
|
||||
private static boolean hitFishingTime(MapleCharacter chr, int baitLevel, double yearLikelihood, double timeLikelihood) {
|
||||
double baitLikelihood = 0.0002 * chr.getWorldServer().getFishingRate() * baitLevel; // can improve 10.0 at "max level 50000" on rate 1x
|
||||
|
||||
if (ServerConstants.USE_DEBUG) {
|
||||
chr.dropMessage(5, "----- FISHING RESULT -----");
|
||||
chr.dropMessage(5, "Likelihoods - Year: " + yearLikelihood + " Time: " + timeLikelihood + " Meso: " + baitLikelihood);
|
||||
chr.dropMessage(5, "Score rolls - Year: " + (0.23 * yearLikelihood) + " Time: " + (0.77 * timeLikelihood) + " Meso: " + baitLikelihood);
|
||||
}
|
||||
|
||||
return (0.23 * yearLikelihood) + (0.77 * timeLikelihood) + (baitLikelihood) > 57.777;
|
||||
}
|
||||
|
||||
public static void doFishing(MapleCharacter chr, int baitLevel, double yearLikelihood, double timeLikelihood){
|
||||
// thanks Fadi, Vcoc for suggesting a custom fishing system
|
||||
|
||||
if (!chr.isLoggedinWorld() || !chr.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameConstants.isFishingArea(chr.getMapId())) {
|
||||
chr.dropMessage("You are not in a fishing area!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chr.getLevel() < 30) {
|
||||
chr.dropMessage(5, "You must be above level 30 to fish!");
|
||||
return;
|
||||
}
|
||||
|
||||
String fishingEffect;
|
||||
if (!hitFishingTime(chr, baitLevel, yearLikelihood, timeLikelihood)) {
|
||||
fishingEffect = "Effect/BasicEff.img/Catch/Fail";
|
||||
} else {
|
||||
String rewardStr = "";
|
||||
fishingEffect = "Effect/BasicEff.img/Catch/Success";
|
||||
|
||||
int rand = (int)(3.0 * Math.random());
|
||||
switch(rand){
|
||||
case 0:
|
||||
int mesoAward = (int)(1400.0 * Math.random() + 1201) * chr.getMesoRate() + (15 * chr.getLevel() / 5);
|
||||
chr.gainMeso(mesoAward, true, true, true);
|
||||
|
||||
rewardStr = mesoAward + " mesos.";
|
||||
break;
|
||||
case 1:
|
||||
int expAward = (int)(645.0 * Math.random() + 620.0) * chr.getExpRate() + (15 * chr.getLevel() / 4);
|
||||
chr.gainExp(expAward, true, true);
|
||||
|
||||
rewardStr = expAward + " EXP.";
|
||||
break;
|
||||
case 2:
|
||||
int itemid = getRandomItem();
|
||||
rewardStr = "a(n) " + MapleItemInformationProvider.getInstance().getName(itemid) + ".";
|
||||
|
||||
if (chr.canHold(itemid)) {
|
||||
chr.getClient().getAbstractPlayerInteraction().gainItem(itemid, true);
|
||||
} else {
|
||||
chr.showHint("Couldn't catch a(n) #r" + MapleItemInformationProvider.getInstance().getName(itemid) + "#k due to #e#b" + ItemConstants.getInventoryType(itemid) + "#k#n inventory limit.");
|
||||
rewardStr += ".. but has goofed up due to full inventory.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
chr.getMap().dropMessage(6, chr.getName() + " found " + rewardStr);
|
||||
}
|
||||
|
||||
chr.announce(MaplePacketCreator.showInfo(fishingEffect));
|
||||
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showForeignInfo(chr.getId(), fishingEffect), false);
|
||||
}
|
||||
|
||||
public static int getRandomItem(){
|
||||
int rand = (int)(100.0 * Math.random());
|
||||
int[] commons = {1002851, 2002020, 2002020, 2000006, 2000018, 2002018, 2002024, 2002027, 2002027, 2000018, 2000018, 2000018 , 2000018, 2002030, 2002018, 2000016}; // filler' up
|
||||
int[] uncommons = {1000025, 1002662, 1002812, 1002850, 1002881, 1002880, 1012072, 4020009, 2043220, 2043022, 2040543, 2044420, 2040943, 2043713, 2044220, 2044120, 2040429, 2043220, 2040943}; // filler' uptoo
|
||||
int[] rares = {1002859, 1002553, 01002762, 01002763, 01002764, 01002765, 01002766, 01002663, 1002788, 1002949, 2049100, 2340000, 2040822,2040822,2040822,2040822,2040822,2040822,2040822,2040822}; // filler' uplast
|
||||
|
||||
if(rand >= 25){
|
||||
return commons[(int)(commons.length * Math.random())];
|
||||
} else if(rand <= 7 && rand >= 4){
|
||||
return uncommons[(int)(uncommons.length * Math.random())];
|
||||
} else {
|
||||
return rares[(int)(rares.length * Math.random())];
|
||||
}
|
||||
}
|
||||
|
||||
private static void debugFishingLikelihood() {
|
||||
long a[] = new long[365], b[] = new long[365];
|
||||
long hits = 0, hits10 = 0, total = 0;
|
||||
|
||||
for (int i = 0; i < 365; i++) {
|
||||
double yearLikelihood = getFishingLikelihood(i);
|
||||
|
||||
int dayHits = 0, dayHits10 = 0;
|
||||
for (int k = 0; k < 24; k++) {
|
||||
for (int l = 0; l < 60; l++) {
|
||||
for (int m = 0; m < 60; m++) {
|
||||
double timeLikelihood = getFishingLikelihood(k + l + m);
|
||||
|
||||
if ((0.23 * yearLikelihood) + (0.77 * timeLikelihood) > 57.777) {
|
||||
hits++;
|
||||
dayHits++;
|
||||
}
|
||||
|
||||
if ((0.23 * yearLikelihood) + (0.77 * timeLikelihood) + 10.0 > 57.777) {
|
||||
hits10++;
|
||||
dayHits10++;
|
||||
}
|
||||
|
||||
total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a[i] = dayHits;
|
||||
b[i] = dayHits10;
|
||||
}
|
||||
|
||||
long maxhit = 0, minhit = Long.MAX_VALUE;
|
||||
for (int i = 0; i < 365; i++) {
|
||||
if (maxhit < a[i]) {
|
||||
maxhit = a[i];
|
||||
}
|
||||
|
||||
if (minhit > a[i]) {
|
||||
minhit = a[i];
|
||||
}
|
||||
}
|
||||
|
||||
long maxhit10 = 0, minhit10 = Long.MAX_VALUE;
|
||||
for (int i = 0; i < 365; i++) {
|
||||
if (maxhit10 < b[i]) {
|
||||
maxhit10 = b[i];
|
||||
}
|
||||
|
||||
if (minhit10 > b[i]) {
|
||||
minhit10 = b[i];
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Diary min " + minhit + " max " + maxhit);
|
||||
System.out.println("Diary10 min " + minhit10 + " max " + maxhit10);
|
||||
System.out.println("Hits: " + hits + "Hits10: " + hits10 + " Total: " + total + " -- %1000: " + (hits * 1000 / total) + ", +10 %1000: " + (hits10 * 1000 / total));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user