Abstract channel schedulers + Mob animation track + More portal SFX

Implemented an improved scheduler system for channels, on where Runnables objects are "registered in" to run on a scheduled future time (effective run time will depend on the proc time of the worker acting under-the-hood).
Implemented a channel scheduler for detecting "mobs currently on animation state". This allows the server to send info to the client about whether a mob should cast a skill or not at that moment.
Improved concurrent protection on MapleMonster listeners registry.
Improved resource deallocation when destroying a monster object.
Added a server flag to allow clean slates to be used on equipments even on the "only successfully used scroll slots" case.
Fixed a critical deadlock case with MapleServerHandler.
Added the portal SFX for many scripted portals that still lacked the sound effect.
This commit is contained in:
ronancpl
2018-07-08 18:25:48 -03:00
parent 94425ba616
commit 5dc16d0cab
42 changed files with 1226 additions and 161 deletions

View File

@@ -0,0 +1,129 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package tools.dropspider;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import provider.MapleData;
import provider.MapleDataDirectoryEntry;
import provider.MapleDataFileEntry;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
import server.MapleItemInformationProvider;
import tools.Pair;
/**
*
* @author Simon
*/
public class DataTool {
private static Map<String, Integer> hardcodedMobs = new HashMap<>();
private static ArrayList<Pair<Integer, String>> npc_list = null;
private static LinkedList<Pair<Integer, String>> mob_pairs = null;
private static MapleDataProvider data = MapleDataProviderFactory.getDataProvider(MapleDataProviderFactory.fileInWZPath("Mob.wz"));
private static HashSet<Integer> bosses = null;
public static void setHardcodedMobNames() {
hardcodedMobs.put("Red Slime [2]", 7120103);
hardcodedMobs.put("Gold Slime", 7120105);
hardcodedMobs.put("Nibelung [3]", 8220015);
}
public static void addMonsterIdsFromHardcodedName(List<Integer> monster_ids, String monster_name) {
Integer id = hardcodedMobs.get(monster_name);
if(id != null) {
monster_ids.add(id);
}
}
public static ArrayList<Integer> monsterIdsFromName(String name) {
MapleData data = null;
MapleDataProvider dataProvider = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "String.wz"));
ArrayList<Integer> ret = new ArrayList<>();
data = dataProvider.getData("Mob.img");
if (mob_pairs == null) {
mob_pairs = new LinkedList<>();
for (MapleData mobIdData : data.getChildren()) {
int mobIdFromData = Integer.parseInt(mobIdData.getName());
String mobNameFromData = MapleDataTool.getString(mobIdData.getChildByPath("name"), "NO-NAME");
mob_pairs.add(new Pair<>(mobIdFromData, mobNameFromData));
}
}
for (Pair<Integer, String> mobPair : mob_pairs) {
if (mobPair.getRight().toLowerCase().equals(name.toLowerCase())) {
ret.add(mobPair.getLeft());
}
}
return ret;
}
private static void populateBossList() {
bosses = new HashSet<>();
MapleDataDirectoryEntry mob_data = data.getRoot();
for (MapleDataFileEntry mdfe : mob_data.getFiles()) {
MapleData boss_candidate = data.getData(mdfe.getName());
MapleData monsterInfoData = boss_candidate.getChildByPath("info");
int mid = Integer.valueOf(boss_candidate.getName().replaceAll("[^0-9]", ""));
boolean boss = MapleDataTool.getIntConvert("boss", monsterInfoData, 0) > 0 || mid == 8810018 || mid == 9410066;
if (boss) {
bosses.add(mid);
}
}
}
public static boolean isBoss(int mid) {
if (bosses == null) {
populateBossList();
}
return bosses.contains(mid);
}
public static ArrayList<Integer> itemIdsFromName(String name) {
ArrayList<Integer> ret = new ArrayList<>();
for (Pair<Integer, String> itemPair : MapleItemInformationProvider.getInstance().getAllItems()) {
String item_name = itemPair.getRight().toLowerCase().replaceAll("\\&quot;", "");
item_name = item_name.replaceAll("'", "");
item_name = item_name.replaceAll("\\'", "");
name = name.toLowerCase().replaceAll("\\&quot;", "");
name = name.replaceAll("'", "");
name = name.replaceAll("\\'", "");
if (item_name.equals(name)) {
ret.add(itemPair.getLeft());
return ret;
}
}
return ret;
}
public static ArrayList<Integer> npcIdsFromName(String name) {
MapleDataProvider dataProvider = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "String.wz"));
ArrayList<Integer> ret = new ArrayList<>();
if (npc_list == null) {
ArrayList<Pair<Integer, String>> searchList = new ArrayList<>();
for (MapleData searchData : dataProvider.getData("Npc.img").getChildren()) {
int searchFromData = Integer.parseInt(searchData.getName());
String infoFromData = MapleDataTool.getString(searchData.getChildByPath("name"), "NO-NAME");
searchList.add(new Pair<>(searchFromData, infoFromData));
}
npc_list = searchList;
}
for (Pair<Integer, String> searched : npc_list) {
if (searched.getRight().toLowerCase().contains(name.toLowerCase())) {
ret.add(searched.getLeft());
}
}
return ret;
}
}

View File

@@ -0,0 +1,177 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package tools.dropspider;
import client.inventory.MapleInventoryType;
import constants.ItemConstants;
/**
*
* @author Simon
*/
public class DropEntry {
private int version;
private int item_id;
private int monster_id;
private int chance;
private int mindrop;
private int maxdrop;
public DropEntry(int item_id, int monster_id, int version) {
this.item_id = item_id;
this.monster_id = monster_id;
mindrop = 1;
maxdrop = 1;
chance = calculateChance(item_id);
this.version = version;
}
private int calculateChance(int item_id) {
MapleInventoryType mit = ItemConstants.getInventoryType(item_id);
boolean boss = DataTool.isBoss(monster_id);
int number = (item_id / 1000) % 1000;
switch (mit) {
case EQUIP:
if (boss) {
return 40000;
}
return 700;
case USE:
if (boss) {
mindrop = 1;
maxdrop = 4;
}
switch (number) {
case 0: // normal potions
mindrop = 1;
if (version > 98) {
maxdrop = 5;
}
return 40000;
case 1: // watermelons, pills, speed potions, etc
case 2: // same thing
return 10000;
case 3: // advanced potions from crafting (should not drop)
case 4: // same thing
case 11: // poison mushroom
case 28: // cool items
case 30: // return scrolls
case 46: // gallant scrolls
return 0;
case 10: // strange potions like apples, eggs
case 12: // drakes blood, sap of ancient tree (rare use)
case 20: // salad, fried chicken, dews
case 22: // air bubbles and stuff. ALSO nependeath honey but oh well
case 50: // antidotes and stuff
return 3000;
case 290: // mastery books
if(boss)
return 40000;
else
return 1000;
case 40: // Scrolls
case 41: // Scrolls
case 43: // Scrolls
case 44: // Scrolls
case 48: // pet scrolls
if(boss)
return 10000;
else
return 750;
case 100: // summon bags
case 101: // summon bags
case 102: // summon bags
case 109: // summon bags
case 120: // pet food
case 211: // cliffs special potion
case 240: // rings
case 270: // pheromone, additional weird stuff
case 310: // teleport rock
case 320: // weird drops
case 390: // weird
case 430: // Scripted items
case 440: // jukebox
case 460: // magnifying glass
case 470: // golden hammer
case 490: // crystanol
case 500: // sp reset
return 0;
case 47: // tablets from dragon rider
return 220000;
case 49: // clean slats, potential scroll, ees
case 70: // throwing stars
case 210: // rare monster piece drops
case 330: // bullets
if(boss)
return 2500;
else
return 400;
case 60: // bow arrows
case 61: // crossbow arrows
mindrop = 10;
maxdrop = 50;
return 10000;
case 213: // boss transfrom
return 100000;
case 280: // skill books
if(boss)
return 20000;
else
return 1000;
case 381: // monster book things
case 382:
case 383:
case 384:
case 385:
case 386:
case 387:
case 388:
return 20000;
case 510: // recipes
case 511:
case 512:
return 10000;
default:
return 0;
}
case ETC:
switch (number) {
case 0: // monster pieces
return 200000;
case 4: // crystal ores
case 130: // simulators
case 131: // manuals
return 3000;
case 30: // game pieces
return 10000;
case 32: // misc items
return 10000;
default:
return 7000;
}
default:
return 7000;
}
}
public String getQuerySegment() {
StringBuilder sb = new StringBuilder();
sb.append("(");
sb.append(monster_id);
sb.append(", ");
sb.append(item_id);
sb.append(", ");
sb.append(mindrop);//min
sb.append(", ");
sb.append(maxdrop);//max
sb.append(", ");
sb.append(0);//quest
sb.append(", ");
sb.append(chance);
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,19 @@
package tools.dropspider;
import java.util.LinkedList;
public class Errors {
public String mobName;
public LinkedList<String> wrong = new LinkedList<>();
public String createErrorLog() {
StringBuilder sb = new StringBuilder();
for (String w : wrong) {
sb.append(mobName).append(" : ").append(w).append("\r\n");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,339 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package tools.dropspider;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import javax.net.ssl.HttpsURLConnection;
/**
*
* @author Simon
*/
//NOTE: this tool is currently unsupported since HS started using HTTPS. Missing proper SSL certificates to access Hidden-Street's website.
public class Main {
private static ArrayList<DropEntry> drop_entries = new ArrayList<>();
private static HashMap<String, Errors> problems = new HashMap<>();
// private static final String TEST_STRING = " <a href=\"/items/leftover/ligator-skin\" alt=\"/tip.php?nid=2138\">Ligator Skin</a>, <a href=\"/items/leftover/the-magic-rock\" alt=\"/tip.php?nid=3954\">The Magic Rock</a>, <a href=\"/items/quest/witch-grass-leaves\" alt=\"/tip.php?nid=6129\">Witch Grass Leaves</a> </td> ";
private static final String BASE_URL = "https://bbb.hidden-street.net";
private static final int VERSION = 83;
private static String[] pages = {"1-10", "11-20", "21-30", "31-40", "41-50", "51-60", "61-70", "71-80", "81-90", "91-100"};
private static String[] additionalPages88 = {"101-150", "151-200"};
private static String[] additionalPagesBB = {"101-120,", "121-140", "141-160", "161-180", "181-200"};
public static void main(String[] args) {
System.setProperty("wzpath", "wz");
//DataTool.setHardcodedMobNames();
//parsePage("https://bbb.hidden-street.net/monster/nibelung-3");
crawlProgram();
dumpQuery();
dumpErrors();
}
private static void crawlProgram() {
//parseMonsterSection(TEST_STRING);
for (String s : pages) {
crawlPage("https://bbb.hidden-street.net/monster/" + s);
}
if (VERSION > 92) { // big bang
for (String s : additionalPagesBB) {
crawlPage("https://bbb.hidden-street.net/monster/" + s);
}
crawlPage("https://bbb.hidden-street.net/monster/101-120?page=1"); //page 1's bugged
} else {
for (String s : additionalPages88) {
crawlPage("https://bbb.hidden-street.net/monster/" + s);
}
}
}
private static void crawlPage(String url) { //recursive method
try {
URL page = new URL(url);
//Authenticator.setDefault( new MyAuthenticator()); // todo keystore/truststore pass
HttpsURLConnection http = (HttpsURLConnection)page.openConnection();
http.setAllowUserInteraction(true);
http.setRequestMethod("GET");
http.connect();
InputStream is = http.getInputStream();
Scanner s = new Scanner(is);
String temp_data = "";
while (s.hasNext()) {
temp_data += s.nextLine() + "\n";
}
s.close();
is.close();
while (temp_data.contains("class=\"monster\">")) {
String monster_section = getStringBetween(temp_data, "class=\"monster\">", "</table>");
parseMonsterSection(monster_section);
temp_data = trimUntil(temp_data, "</table>");
}
if (temp_data.contains("Go to next page")) {
String next_url_segment = getStringBetween(temp_data, "<li class=\"pager-next\"><a href=\"", "\" title=\"Go to next page");
String next_url = BASE_URL + next_url_segment;
crawlPage(next_url);
} else {
System.out.println("Finished crawling section.");
}
} catch (MalformedURLException mue) {
mue.printStackTrace();
System.out.println("Error parsing URL: " + url);
return;
} catch (IOException ioe) {
ioe.printStackTrace();
System.out.println("Error reading from URL: " + ioe.getLocalizedMessage());
return;
}
}
private static void parsePage(String url) { //unit method
try {
URL page = new URL(url);
InputStream is = page.openStream();
Scanner s = new Scanner(is);
String temp_data = "";
while (s.hasNext()) {
temp_data += s.nextLine() + "\n";
}
s.close();
is.close();
while (temp_data.contains("class=\"monster\">")) {
String monster_section = getStringBetween(temp_data, "class=\"monster\">", "</table>");
parseMonsterSection(monster_section);
temp_data = trimUntil(temp_data, "</table>");
}
if (temp_data.contains("Go to next page")) {
String next_url_segment = getStringBetween(temp_data, "<li class=\"pager-next\"><a href=\"", "\" title=\"Go to next page");
String next_url = BASE_URL + next_url_segment;
//crawlPage(next_url);
} else {
System.out.println("Finished parsing section.");
}
} catch (MalformedURLException mue) {
mue.printStackTrace();
System.out.println("Error parsing URL: " + url);
return;
} catch (IOException ioe) {
ioe.printStackTrace();
System.out.println("Error reading from URL: " + ioe.getLocalizedMessage());
return;
}
}
private static void parseMonsterSection(String html_data) {
String monster_name = getStringBetween(html_data, "alt=\"", "\" title=");
System.out.println(monster_name);
// System.out.println(html_data);
//parse etc drop
parseItemSection(getStringBetween(html_data, "Etc. drop:</strong>", "</td>"), monster_name);
//parse useable drop
parseItemSection(getStringBetween(html_data, "Useable drop:</strong>", "</td>"), monster_name);
//parse ore drop
parseItemSection(getStringBetween(html_data, "Ore drop:</strong>", "</td>"), monster_name);
//parse equips
parseItemSection(getStringBetween(html_data, "Common equipment:</strong>", "</div>"), monster_name);
parseItemSection(getStringBetween(html_data, "Warrior equipment:</strong>", "</div>"), monster_name);
parseItemSection(getStringBetween(html_data, "Magician equipment:</strong>", "</div>"), monster_name);
parseItemSection(getStringBetween(html_data, "Bowman equipment:</strong>", "</div>"), monster_name);
parseItemSection(getStringBetween(html_data, "Thief equipment:</strong>", "</div>"), monster_name);
parseItemSection(getStringBetween(html_data, "Pirate equipment:</strong>", "</div>"), monster_name);
//System.out.println(monster_name);
}
private static void parseItemSection(String html_data, String monster_name) {
String temp_data = html_data;
while (temp_data.contains("<a href")) {
// System.out.println("Temp_data: " + temp_data);
String s1 = trimUntil(temp_data, ">");
String item_name = getStringBetween(s1, "", "</a>");
temp_data = trimUntil(temp_data, "</a>");
boolean gender_equip = false;
if (item_name.contains("(M)") || item_name.contains("(F)")) {
item_name = item_name.replaceAll("(\\(M\\))|(\\(F\\))", "");
gender_equip = true;
}
item_name = item_name.replaceAll("Throwing-Star", "Throwing-Stars").trim();
item_name = item_name.replaceAll("for Magic Attack", "for Magic Att.").trim();
item_name = item_name.replaceAll("\\(50%\\)", "").trim();
item_name = item_name.replaceAll("\\(70%\\)", "").trim();
item_name = item_name.replaceAll("\\'s", "").trim();
monster_name = monster_name.replaceAll("Horntail\\'s Head B", "Horntail");
// Process scrolls, nexon doesn't have the % on most of the scrolls. So we need to remove it
// Unfortunately they do for some, so we have to handle that too.
boolean scroll = false;
int scrollType = 0;
if(item_name.contains("100%")) {
scroll = true;
item_name = item_name.replaceAll("100%", "").trim();
item_name = item_name.replaceAll("\\(\\)", "").trim(); // Hidden Street has a few scroll %'s with ()s around them.. sigh
} else if(item_name.contains("60%")) {
scroll = true;
scrollType = 1;
item_name = item_name.replaceAll("60%", "").trim();
item_name = item_name.replaceAll("\\(\\)", "").trim();
} else if(item_name.contains("10%")) {
scroll = true;
scrollType = 2;
item_name = item_name.replaceAll("10%", "").trim();
item_name = item_name.replaceAll("\\(\\)", "").trim();
//f(item_name.contains(" ()")) item_name = item_name.substring(0, item_name.lastIndexOf(" ("));
} else if(item_name.contains("70%")) {
scroll = true;
scrollType = 4;
item_name = item_name.replaceAll("70%", "").trim();
item_name = item_name.replaceAll("\\(\\)", "").trim();
} else if(item_name.contains("30%")) {
scroll = true;
scrollType = 5;
item_name = item_name.replaceAll("30%", "").trim();
item_name = item_name.replaceAll("\\(\\)", "").trim();
}
// System.out.println("Item name: " + item_name);
//drop entry
ArrayList<Integer> monster_ids = DataTool.monsterIdsFromName(monster_name);
//DataTool.addMonsterIdsFromHardcodedName(monster_ids, monster_name);
ArrayList<Integer> item_ids = DataTool.itemIdsFromName(item_name);
if(scroll && item_ids.isEmpty()) {
// Try adding on the % again. Thanks nexon...
if(scrollType == 0) item_name += " 100%";
if(scrollType == 1) item_name += " 60%";
if(scrollType == 2) item_name += " 10%";
if(scrollType == 4) item_name += " 70%";
if(scrollType == 5) item_name += " 30%";
item_ids = DataTool.itemIdsFromName(item_name);
}
if (!monster_ids.isEmpty() && !item_ids.isEmpty()) {
int item_id = item_ids.get(0);
if(scroll) {
item_id += scrollType;
}
int item_id_2 = -1;
for (Integer mob_id : monster_ids) {
System.out.println("Monster ID: " + mob_id + ", Item ID: " + item_id);
drop_entries.add(new DropEntry(item_id, mob_id, VERSION));
if (gender_equip && item_ids.size() > 1) {
item_id_2 = item_ids.get(1);
drop_entries.add(new DropEntry(item_id_2, mob_id, VERSION));
}
}
} else {
System.out.println("Error parsing item " + item_name + " dropped by " + monster_name + ".");
if (!monster_ids.isEmpty()) {
if (!problems.containsKey(monster_name)) {
Errors e = new Errors();
e.mobName = monster_name;
problems.put(monster_name, e);
}
problems.get(monster_name).wrong.add(item_name);
}
//System.out.println("Monster ids size: " + monster_ids.size() + ", Item IDs size: " + item_ids.size());
}
}
}
/**
* Returns the string lying between the two specified strings.
*
* @param line The string to parse
* @param start The first string
* @param end The last string
* @return The string between the two specified strings
*/
public static String getStringBetween(String line, String start, String end) {
int start_offset = line.indexOf(start) + start.length();
return line.substring(start_offset, line.substring(start_offset).indexOf(end) + start_offset);
}
public static String trimUntil(String line, String until) {
int until_pos = line.indexOf(until);
if (until_pos == -1) {
return null;
} else {
return line.substring(until_pos + until.length());
}
}
public static void dumpErrors() {
String file = "errors.txt";
try {
File f = new File(file);
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
PrintWriter pw = new PrintWriter(bw);
for (Errors err : problems.values()) {
pw.write(err.createErrorLog());
}
pw.flush();
pw.close();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void dumpQuery() {
String filename = "drops.sql";
try {
File output = new File(filename);
BufferedWriter bw = new BufferedWriter(new FileWriter(output));
PrintWriter pw = new PrintWriter(bw);
StringBuilder sb = new StringBuilder();
pw.write("TRUNCATE TABLE `drop_data`;\r\n");
pw.write("INSERT INTO `drop_data` (`dropperid`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`) VALUES ");
for (Iterator<DropEntry> i = drop_entries.iterator(); i.hasNext();) {
DropEntry de = i.next();
pw.write(de.getQuerySegment());
if (i.hasNext()) {
pw.write(", \r\n");
}
}
pw.write(sb.toString());
pw.close();
bw.close();
} catch (IOException ioe) {
ioe.printStackTrace();
System.out.println("Error writing to file: " + ioe.getLocalizedMessage());
}
}
}

View File

@@ -45,7 +45,11 @@ public enum MonitoredLockType {
SERVER_DISEASES,
MERCHANT,
CHANNEL,
CHANNEL_MOBACTION,
CHANNEL_MOBANIMAT,
CHANNEL_MOBSTATUS,
CHANNEL_OVTSTATUS,
CHANNEL_OVERALL,
GUILD,
PARTY,
WORLD_PARTY,
@@ -63,8 +67,9 @@ public enum MonitoredLockType {
CASHSHOP,
VISITOR_PSHOP,
STORAGE,
MOB_EXT,
MOB,
MOB_ANI,
MOB_EXT,
MOB_STATI,
MOBSKILL_FACTORY,
PORTAL,