Compare commits

...

37 Commits

Author SHA1 Message Date
Ponk
c609bcf2ee Merge pull request #193 from P0nk/fix-192/playernpc #patch
Fix #192 - Unable to create PlayerNPC
2023-09-19 17:15:00 +02:00
P0nk
0aee9d7c3f Fix unable to create playernpc
The initialization of the running world counters depended on worlds already having been initialized, so I made that dependency more explicit.
2023-09-19 12:56:51 +02:00
P0nk
a49c1703ae Remove developer player npc 2023-09-18 23:26:37 +02:00
Ponk
38eecd0db7 Merge pull request #188 from leevccc/master #patch
fix: error flag after use karma
2023-09-09 07:56:33 +02:00
leevccc
e320bafa8b fix: error flag after use karma
setFalg() function is designed to take arguments of type short. Forcing the short type flag to be converted to the byte type causes some errors here.

For example, the equipment merge system will make the UNTRADEABLE flag become 520, combined with the Scissors of Karma 16, and finally become 536, this value is beyond the byte range, and the mandatory variable type will cause the flag error, the specific problem I encountered is that the MERGE_UNTRADEABLE flag is lost after using the Scissors of Karma.
2023-09-08 04:59:56 +08:00
Ponk
754e5e61f2 Merge pull request #185 from Arnuh/fix-client-local-address #patch
Fix masking of ip address when connecting locally
2023-08-20 18:26:59 +02:00
Ponk
afba85827a Merge pull request #186 from sashimi-yzh/fix-cats-eye-dropper #patch
fix the dropper of Cat's Eye
2023-08-20 18:21:20 +02:00
Zihao Yu
8cd5211b8b fix the dropper of Cat's Eye
* it was previously dropped from Scorpion, which is wrong
2023-08-20 22:55:43 +08:00
Arnah
2d7525f2b4 Fix cashshop exiting not using proper ip 2023-08-16 16:19:16 -06:00
Arnah
7adb25888f Stop masking ip address when connecting locally 2023-08-16 00:53:40 -06:00
Ponk
b46912afcb Merge pull request #181 from Silwhoon/fix-incorrect-orange-potion-id #patch
Changes an incorrect Orange Potion ID to the correct one
2023-08-15 19:34:44 +02:00
Ponk
ed3d4823b2 Merge pull request #184 from noamyoyo/master #patch
Expose database container to allow access from MySQL client such as Workbench.
2023-08-15 19:32:09 +02:00
noampfeifel
b8a360917e final fixes 2023-08-15 19:07:34 +03:00
noampfeifel
26dbe36a15 fixed ports to simple setup, added notes about DB_HOST 2023-08-15 19:06:05 +03:00
noampfeifel
396447519d fixing compose port and config string for db 2023-08-13 17:33:25 +03:00
pleb
83e436bbd2 Changes an incorrect Orange Potion ID to the correct one
Thorr in Mushroom Kingdom sold an untradable "Orange Potion for Beginners" instead of a "Orange Potion". This fixes that.
2023-07-27 09:03:39 -05:00
Ponk
d307eff71f Merge pull request #177 from LynxStar/patch/mini-dungeons #patch
Fix mini-dungeon portals for parties
2023-07-10 23:09:51 +02:00
Arthur Charlton
b935725096 Handle the potentially null portal based on the name.
Matches the behavior of warping a character to a named portal.
2023-07-10 17:01:48 -04:00
Ponk
12248acd7b Merge pull request #178 from LynxStar/patch/delivery-system #patch
Delivery System - Invalid Recipient Name Patch
2023-07-10 22:40:57 +02:00
Arthur Charlton
07eb0f5e8e Untangle nested control flow conditions. 2023-07-05 14:16:09 -04:00
Arthur Charlton
cfb5fc25c3 Calling code assumes the pair never null and uses -1 to signify not found. 2023-07-05 14:12:42 -04:00
Arthur Charlton
3816e1c5bd Add the warp party function that the scripts use 2023-07-03 18:25:14 -04:00
Ponk
c9d551cd39 Merge pull request #173 from MatthewHinds/gm-security #minor
GM security changes to prevent item/mesos abuse
2023-05-29 15:50:03 +02:00
Matthew Hinds
95bf0473f3 Adjusted based on feedback 2023-05-29 14:32:01 +12:00
Matthew Hinds
a9d92b78a2 Meso drop restricted by GM level 2023-05-29 12:57:01 +12:00
Ponk
b8ebace039 Merge pull request #174 from Favouris/master #patch
Rename Monster Carnival portal scripts
2023-05-15 12:00:44 +02:00
Favouris
9223957931 Rename MCRevive6.js to MCrevive6.js 2023-05-14 19:17:38 +08:00
Favouris
5cddb7f2b6 Rename MCRevive5.js to MCrevive5.js 2023-05-14 19:17:25 +08:00
Favouris
08e7a3af16 Rename MCRevive4.js to MCrevive4.js 2023-05-14 19:17:15 +08:00
Favouris
1416cd432d Rename MCRevive3.js to MCrevive3.js 2023-05-14 19:17:03 +08:00
Favouris
2d6cf07a65 Rename MCRevive2.js to MCrevive2.js 2023-05-14 19:16:51 +08:00
Favouris
0b73d6112a Rename MCRevive1.js to MCrevive1.js 2023-05-14 19:16:40 +08:00
Ponk
b742ac0591 Merge pull request #172 from MatthewHinds/remove-levelup-messages #minor
Removed level up messages as it is non GMS like and a bit cringey
2023-05-12 08:57:51 +02:00
Matthew Hinds
4546fd44ff To prevent abuse, GMs should be permission restricted (via GM level) to trade with other non GM players, use their storage (prevent transferring to their other characters), send via Duey and to drop items. GM level is configurable. 2023-05-12 18:02:08 +12:00
Matthew Hinds
216fa9341b Removed level up messages as it is non GMS like and a bit cringey 2023-05-12 14:47:18 +12:00
Ponk
1d6d5dcc94 Merge pull request #171 from srcyscrt/docker-eclipse-temurin #patch
Use Eclipse Temurin images in the Dockerfile
2023-04-22 17:33:43 +02:00
srcyscrt
61f451694f Use Eclipse Temurin images in the Dockerfile 2023-04-22 18:43:23 +08:00
28 changed files with 154 additions and 284 deletions

View File

@@ -4,7 +4,7 @@
#
# Cosmic JAR creation stage
#
FROM maven:3.8.4-openjdk-17 AS jar
FROM maven:3.9.1-eclipse-temurin-17 AS jar
# Build in a separated location which won't have permissions issues.
WORKDIR /opt/cosmic
@@ -21,7 +21,7 @@ RUN mvn -f ./pom.xml clean package -Dmaven.test.skip -T 1C
#
# Server creation stage
#
FROM openjdk:17.0.2
FROM eclipse-temurin:17.0.6_10-jre
# Host the server in a location that won't have permissions issues.
WORKDIR /opt/server

View File

@@ -159,8 +159,8 @@ worlds:
server:
#Database Configuration
DB_URL_FORMAT: "jdbc:mysql://%s:3306/cosmic"
DB_HOST: "localhost"
DB_URL_FORMAT: "jdbc:mysql://%s:3306/cosmic" # If the docker ENV for DB_HOST is anything but "db", this string format should be changed from 3306 to 3307 (or whichever port it was changed to in docker)
DB_HOST: "localhost"
DB_USER: "cosmic_server"
DB_PASS: "snailshell"
INIT_CONNECTION_POOL_TIMEOUT: 90 # Seconds
@@ -461,6 +461,12 @@ server:
#Event End Timestamp
EVENT_END_TIMESTAMP: 1428897600000
# GM Security Configuration
MINIMUM_GM_LEVEL_TO_TRADE: 4
MINIMUM_GM_LEVEL_TO_USE_STORAGE: 4
MINIMUM_GM_LEVEL_TO_USE_DUEY: 4
MINIMUM_GM_LEVEL_TO_DROP: 4
#Any NPC ids that should search for a js override script (useful if they already have wz entries since otherwise they're ignored).
NPCS_SCRIPTABLE:
#9200000: Talk to Cody # Cody

View File

@@ -19993,7 +19993,7 @@ USE `cosmic`;
(8220015, 1452015, 1, 1, 0, 22000),
(8220015, 1472031, 1, 1, 0, 22000),
(8220015, 1482010, 1, 1, 0, 22000),
(2110301, 4031568, 1, 1, 3911, 80000),
(2100108, 4031568, 1, 1, 3911, 80000),
(9300150, 4031774, 1, 1, 3361, 100000),
(9300150, 4031796, 1, 1, 3362, 100000),
(9300105, 4001118, 1, 1, 3814, 200000),

View File

@@ -335,7 +335,7 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(1301000, 2000006, 620, 0, 156),
(1301000, 2000003, 200, 0, 160),
(1301000, 2000002, 320, 0, 164),
(1301000, 2000015, 160, 0, 168),
(1301000, 2000001, 160, 0, 168),
(1301000, 2000000, 50, 0, 172);
# adding missing pirate items at Singapore npc's

View File

@@ -18,7 +18,7 @@ services:
- ./scripts:/opt/server/scripts
- ./wz:/opt/server/wz
environment:
DB_HOST: "db"
DB_HOST: "db" ## Remember if this is present it will OVERRIDE the host in the config.yaml, if you put here anything other than db, you'll need to change the config.yaml jdbc string to port 3307, and not port 3306
db:
image: mysql:8.0.23
@@ -27,6 +27,8 @@ services:
MYSQL_DATABASE: "cosmic"
MYSQL_USER: "cosmic_server"
MYSQL_PASSWORD: "snailshell"
ports:
- "3307:3306"
volumes:
- ./database/docker-db-data:/var/lib/mysql
- ./database/sql:/docker-entrypoint-initdb.d

View File

@@ -6482,7 +6482,6 @@ public class Character extends AbstractCharacterObject {
ThreadManager.getInstance().newTask(r);
}
levelUpMessages();
guildUpdate();
FamilyEntry familyEntry = getFamilyEntry();
@@ -6521,94 +6520,6 @@ public class Character extends AbstractCharacterObject {
return false;
}
}
private void levelUpMessages() {
if (level % 5 != 0) { //Performance FTW?
return;
}
if (level == 5) {
yellowMessage("Aww, you're level 5, how cute!");
} else if (level == 10) {
yellowMessage("Henesys Party Quest is now open to you! Head over to Henesys, find some friends, and try it out!");
} else if (level == 15) {
yellowMessage("Half-way to your 2nd job advancement, nice work!");
} else if (level == 20) {
yellowMessage("You can almost Kerning Party Quest!");
} else if (level == 25) {
yellowMessage("You seem to be improving, but you are still not ready to move on to the next step.");
} else if (level == 30) {
yellowMessage("You have finally reached level 30! Try job advancing, after that try the Mushroom Castle!");
} else if (level == 35) {
yellowMessage("Hey did you hear about this mall that opened in Kerning? Try visiting the Kerning Mall.");
} else if (level == 40) {
yellowMessage("Do @rates to see what all your rates are!");
} else if (level == 45) {
yellowMessage("I heard that a rock and roll artist died during the grand opening of the Kerning Mall. People are naming him the Spirit of Rock.");
} else if (level == 50) {
yellowMessage("You seem to be growing very fast, would you like to test your new found strength with the mighty Zakum?");
} else if (level == 55) {
yellowMessage("You can now try out the Ludibrium Maze Party Quest!");
} else if (level == 60) {
yellowMessage("Feels good to be near the end of 2nd job, doesn't it?");
} else if (level == 65) {
yellowMessage("You're only 5 more levels away from 3rd job, not bad!");
} else if (level == 70) {
yellowMessage("I see many people wearing a teddy bear helmet. I should ask someone where they got it from.");
} else if (level == 75) {
yellowMessage("You have reached level 3 quarters!");
} else if (level == 80) {
yellowMessage("You think you are powerful enough? Try facing horntail!");
} else if (level == 85) {
yellowMessage("Did you know? The majority of people who hit level 85 in Cosmic don't live to be 85 years old?");
} else if (level == 90) {
yellowMessage("Hey do you like the amusement park? I heard Spooky World is the best theme park around. I heard they sell cute teddy-bears.");
} else if (level == 95) {
yellowMessage("100% of people who hit level 95 in Cosmic don't live to be 95 years old.");
} else if (level == 100) {
yellowMessage("Mid-journey so far... You just reached level 100! Now THAT's such a feat, however to manage the 200 you will need even more passion and determination than ever! Good hunting!");
} else if (level == 105) {
yellowMessage("Have you ever been to leafre? I heard they have dragons!");
} else if (level == 110) {
yellowMessage("I see many people wearing a teddy bear helmet. I should ask someone where they got it from.");
} else if (level == 115) {
yellowMessage("I bet all you can think of is level 120, huh? Level 115 gets no love.");
} else if (level == 120) {
yellowMessage("Are you ready to learn from the masters? Head over to your job instructor!");
} else if (level == 125) {
yellowMessage("The struggle for mastery books has begun, huh?");
} else if (level == 130) {
yellowMessage("You should try Temple of Time. It should be pretty decent EXP.");
} else if (level == 135) {
yellowMessage("I hope you're still not struggling for mastery books!");
} else if (level == 140) {
yellowMessage("You're well into 4th job at this point, great work!");
} else if (level == 145) {
yellowMessage("Level 145 is serious business!");
} else if (level == 150) {
yellowMessage("You have becomed quite strong, but the journey is not yet over.");
} else if (level == 155) {
yellowMessage("At level 155, Zakum should be a joke to you. Nice job!");
} else if (level == 160) {
yellowMessage("Level 160 is pretty impressive. Try taking a picture and putting it on Instagram.");
} else if (level == 165) {
yellowMessage("At this level, you should start looking into doing some boss runs.");
} else if (level == 170) {
yellowMessage("Level 170, huh? You have the heart of a champion.");
} else if (level == 175) {
yellowMessage("You came a long way from level 1. Amazing job so far.");
} else if (level == 180) {
yellowMessage("Have you ever tried taking a boss on by yourself? It is quite difficult.");
} else if (level == 185) {
yellowMessage("Legend has it that you're a legend.");
} else if (level == 190) {
yellowMessage("You only have 10 more levels to go until you hit 200!");
} else if (level == 195) {
yellowMessage("Nothing is stopping you at this point, level 195!");
} else if (level == 200) {
yellowMessage("Very nicely done! You have reached the so-long dreamed LEVEL 200!!! You are truly a hero among men, cheers upon you!");
}
}
public void setPlayerRates() {
this.expRate *= GameConstants.getPlayerBonusExpRate(this.level / 20);
this.mesoRate *= GameConstants.getPlayerBonusMesoRate(this.level / 20);

View File

@@ -39,7 +39,6 @@ import net.server.Server;
import net.server.channel.Channel;
import net.server.coordinator.login.LoginBypassCoordinator;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.IpAddresses;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.guild.Guild;
@@ -176,10 +175,7 @@ public class Client extends ChannelInboundHandlerAdapter {
private static String getRemoteAddress(io.netty.channel.Channel channel) {
String remoteAddress = "null";
try {
String hostAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
if (hostAddress != null) {
remoteAddress = IpAddresses.evaluateRemoteAddress(hostAddress); // thanks dyz for noticing Local/LAN/WAN connections not interacting properly
}
remoteAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
} catch (NullPointerException npe) {
log.warn("Unable to get remote address for client", npe);
}

View File

@@ -706,6 +706,12 @@ public class InventoryManipulator {
Inventory inv = chr.getInventory(type);
Item source = inv.getItem(src);
if (chr.isGM() && chr.gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_DROP) {
chr.message("You cannot drop items at your GM level.");
log.info("GM %s tried to drop item id %d", chr.getName(), source.getItemId());
return;
}
if (chr.getTrade() != null || chr.getMiniGame() != null || source == null) { //Only check needed would prob be merchants (to see if the player is in one)
return;
}

View File

@@ -43,7 +43,7 @@ public class KarmaManipulator {
flag ^= karmaFlag;
flag |= ItemConstants.UNTRADEABLE;
item.setFlag((byte) flag);
item.setFlag(flag);
}
}
@@ -53,6 +53,6 @@ public class KarmaManipulator {
flag |= karmaFlag;
flag &= (0xFFFFFFFF ^ ItemConstants.UNTRADEABLE);
item.setFlag((byte) flag);
item.setFlag(flag);
}
}

View File

@@ -92,14 +92,15 @@ public class DueyProcessor {
}
private static Pair<Integer, Integer> getAccountCharacterIdFromCNAME(String name) {
Pair<Integer, Integer> ids = null;
Pair<Integer, Integer> ids = new Pair<>(-1, -1);
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT id,accountid FROM characters WHERE name = ?")) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
ids = new Pair<>(rs.getInt("accountid"), rs.getInt("id"));
ids.left = rs.getInt("accountid");
ids.right = rs.getInt("id");
}
}
} catch (SQLException e) {
@@ -284,6 +285,13 @@ public class DueyProcessor {
public static void dueySendItem(Client c, byte invTypeId, short itemPos, short amount, int sendMesos, String sendMessage, String recipient, boolean quick) {
if (c.tryacquireClient()) {
try {
if (c.getPlayer().isGM() && c.getPlayer().gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_USE_DUEY) {
c.getPlayer().message("You cannot use Duey to send items at your GM level.");
log.info(String.format("GM %s tried to send a package to %s", c.getPlayer().getName(), recipient));
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_INCORRECT_REQUEST.getCode()));
return;
}
int fee = Trade.getFee(sendMesos);
if (sendMessage != null && sendMessage.length() > 100) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with Quick Delivery on duey.");
@@ -308,30 +316,25 @@ public class DueyProcessor {
return;
}
Pair<Integer, Integer> accIdCid;
if (c.getPlayer().getMeso() >= finalcost) {
accIdCid = getAccountCharacterIdFromCNAME(recipient);
int recipientAccId = accIdCid.getLeft();
if (recipientAccId != -1) {
if (recipientAccId == c.getAccID()) {
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_SAMEACC_ERROR.getCode()));
return;
}
} else {
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_NAME_DOES_NOT_EXIST.getCode()));
return;
}
} else {
if(c.getPlayer().getMeso() < finalcost) {
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_NOT_ENOUGH_MESOS.getCode()));
return;
}
int recipientCid = accIdCid.getRight();
if (recipientCid == -1) {
var accIdCid = getAccountCharacterIdFromCNAME(recipient);
var recipientAccId = accIdCid.getLeft();
var recipientCid = accIdCid.getRight();
if (recipientAccId == -1 || recipientCid == -1) {
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_NAME_DOES_NOT_EXIST.getCode()));
return;
}
if (recipientAccId == c.getAccID()) {
c.sendPacket(PacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_SAMEACC_ERROR.getCode()));
return;
}
if (quick) {
InventoryManipulator.removeById(c, InventoryType.CASH, ItemId.QUICK_DELIVERY_TICKET, (short) 1, false, false);
}

View File

@@ -50,6 +50,8 @@ public class StorageProcessor {
ItemInformationProvider ii = ItemInformationProvider.getInstance();
Character chr = c.getPlayer();
Storage storage = chr.getStorage();
String gmBlockedStorageMessage = "You cannot use the storage as a GM of this level.";
byte mode = p.readByte();
if (chr.getLevel() < 15) {
@@ -61,7 +63,7 @@ public class StorageProcessor {
if (c.tryacquireClient()) {
try {
switch (mode) {
case 4: { // take out
case 4: { // Take out
byte type = p.readByte();
byte slot = p.readByte();
if (slot < 0 || slot > storage.getSlots()) { // removal starts at zero
@@ -70,8 +72,17 @@ public class StorageProcessor {
c.disconnect(true, false);
return;
}
slot = storage.getSlot(InventoryType.getByType(type), slot);
Item item = storage.getItem(slot);
if (hasGMRestrictions(chr)) {
chr.dropMessage(1, gmBlockedStorageMessage);
log.info(String.format("GM %s blocked from using storage", chr.getName()));
chr.sendPacket(PacketCreator.enableActions());
return;
}
if (item != null) {
if (ii.isPickupRestricted(item.getItemId()) && chr.haveItemWithId(item.getItemId(), true)) {
c.sendPacket(PacketCreator.getStorageError((byte) 0x0C));
@@ -107,7 +118,7 @@ public class StorageProcessor {
}
break;
}
case 5: { // store
case 5: { // Store
short slot = p.readShort();
int itemId = p.readInt();
short quantity = p.readShort();
@@ -120,6 +131,14 @@ public class StorageProcessor {
c.disconnect(true, false);
return;
}
if (hasGMRestrictions(chr)) {
chr.dropMessage(1, gmBlockedStorageMessage);
log.info(String.format("GM %s blocked from using storage", chr.getName()));
chr.sendPacket(PacketCreator.enableActions());
return;
}
if (quantity < 1) {
c.sendPacket(PacketCreator.enableActions());
return;
@@ -173,16 +192,24 @@ public class StorageProcessor {
}
break;
}
case 6: // arrange items
case 6: // Arrange items
if (YamlConfig.config.server.USE_STORAGE_ITEM_SORT) {
storage.arrangeItems(c);
}
c.sendPacket(PacketCreator.enableActions());
break;
case 7: { // meso
case 7: { // Mesos
int meso = p.readInt();
int storageMesos = storage.getMeso();
int playerMesos = chr.getMeso();
if (hasGMRestrictions(chr)) {
chr.dropMessage(1, gmBlockedStorageMessage);
log.info(String.format("GM %s blocked from using storage", chr.getName()));
chr.sendPacket(PacketCreator.enableActions());
return;
}
if ((meso > 0 && storageMesos >= meso) || (meso < 0 && playerMesos >= -meso)) {
if (meso < 0 && (storageMesos - meso) < 0) {
meso = Integer.MIN_VALUE + storageMesos;
@@ -208,7 +235,7 @@ public class StorageProcessor {
}
break;
}
case 8: // close... unless the player decides to enter cash shop!
case 8: // Close (unless the player decides to enter cash shop)
storage.close();
break;
}
@@ -217,4 +244,8 @@ public class StorageProcessor {
}
}
}
private static boolean hasGMRestrictions(Character character) {
return character.isGM() && character.gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_USE_STORAGE;
}
}

View File

@@ -309,6 +309,12 @@ public class ServerConfig {
//Event End Timestamp
public long EVENT_END_TIMESTAMP;
//GM Security Configuration
public int MINIMUM_GM_LEVEL_TO_TRADE;
public int MINIMUM_GM_LEVEL_TO_USE_STORAGE;
public int MINIMUM_GM_LEVEL_TO_USE_DUEY;
public int MINIMUM_GM_LEVEL_TO_DROP;
//Custom NPC overrides. List of NPC IDs.
public Map<String, String> NPCS_SCRIPTABLE = new HashMap<>();
}

View File

@@ -15,7 +15,6 @@ import net.encryption.InitializationVector;
import net.encryption.PacketCodec;
import net.packet.logging.InPacketLogger;
import net.packet.logging.OutPacketLogger;
import net.server.coordinator.session.IpAddresses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.PacketCreator;
@@ -35,10 +34,7 @@ public abstract class ServerChannelInitializer extends ChannelInitializer<Socket
String getRemoteAddress(Channel channel) {
String remoteAddress = "null";
try {
String hostAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
if (hostAddress != null) {
remoteAddress = IpAddresses.evaluateRemoteAddress(hostAddress); // thanks dyz for noticing Local/LAN/WAN connections not interacting properly
}
remoteAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
} catch (NullPointerException npe) {
log.warn("Unable to get remote address from netty Channel: {}", channel, npe);
}

View File

@@ -57,7 +57,7 @@ import server.SkillbookInformationProvider;
import server.ThreadManager;
import server.TimerManager;
import server.expeditions.ExpeditionBossLog;
import server.life.PlayerNPCFactory;
import server.life.PlayerNPC;
import server.quest.Quest;
import service.NoteService;
import tools.DatabaseConnection;
@@ -849,15 +849,15 @@ public class Server {
final ExecutorService initExecutor = Executors.newFixedThreadPool(10);
// Run slow operations asynchronously to make startup faster
final List<Future<?>> futures = new ArrayList<>();
futures.add(initExecutor.submit(() -> SkillFactory.loadAllSkills()));
futures.add(initExecutor.submit(() -> CashItemFactory.loadAllCashItems()));
futures.add(initExecutor.submit(() -> Quest.loadAllQuests()));
futures.add(initExecutor.submit(() -> SkillbookInformationProvider.loadAllSkillbookInformation()));
futures.add(initExecutor.submit(() -> PlayerNPCFactory.loadFactoryMetadata()));
futures.add(initExecutor.submit(SkillFactory::loadAllSkills));
futures.add(initExecutor.submit(CashItemFactory::loadAllCashItems));
futures.add(initExecutor.submit(Quest::loadAllQuests));
futures.add(initExecutor.submit(SkillbookInformationProvider::loadAllSkillbookInformation));
initExecutor.shutdown();
TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE));
final int worldCount = Math.min(GameConstants.WORLD_NAMES.length, YamlConfig.config.server.WORLDS);
try (Connection con = DatabaseConnection.getConnection()) {
setAllLoggedOut(con);
setAllMerchantsInactive(con);
@@ -868,6 +868,7 @@ public class Server {
CashIdGenerator.loadExistentCashIdsFromDb(con);
applyAllNameChanges(con); // -- name changes can be missed by INSTANT_NAME_CHANGE --
applyAllWorldTransfers(con);
PlayerNPC.loadRunningRankData(con, worldCount);
} catch (SQLException sqle) {
log.error("Failed to run all startup-bound database tasks", sqle);
throw new IllegalStateException(sqle);
@@ -877,8 +878,6 @@ public class Server {
initializeTimelyTasks(channelDependencies); // aggregated method for timely tasks thanks to lxconan
try {
int worldCount = Math.min(GameConstants.WORLD_NAMES.length, YamlConfig.config.server.WORLDS);
for (int i = 0; i < worldCount; i++) {
initWorld();
}

View File

@@ -29,6 +29,7 @@ import constants.id.ItemId;
import constants.id.MapId;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.Trade;
@@ -181,7 +182,11 @@ public final class ChangeMapHandler extends AbstractPacketHandler {
c.disconnect(false, false);
return;
}
String[] socket = c.getChannelServer().getIP().split(":");
String[] socket = Server.getInstance().getInetSocket(c, c.getWorld(), c.getChannel());
if (socket == null) {
c.enableCSActions();
return;
}
chr.getCashShop().open(false);
chr.setSessionTransitionState();

View File

@@ -23,6 +23,7 @@ package net.server.channel.handlers;
import client.Character;
import client.Client;
import config.YamlConfig;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import tools.PacketCreator;
@@ -42,6 +43,11 @@ public final class MesoDropHandler extends AbstractPacketHandler {
p.skip(4);
int meso = p.readInt();
if (player.isGM() && player.gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_DROP) {
player.message("You cannot drop mesos at your GM level.");
return;
}
if (c.tryacquireClient()) { // thanks imbee for noticing players not being able to throw mesos too fast
try {
if (meso <= player.getMeso() && meso > 9 && meso < 50001) {

View File

@@ -1,7 +1,5 @@
package net.server.coordinator.session;
import config.YamlConfig;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -16,14 +14,6 @@ public class IpAddresses {
.collect(Collectors.toList());
}
public static String evaluateRemoteAddress(String inetAddress) {
if (isLocalAddress(inetAddress) || isLanAddress(inetAddress)) {
return YamlConfig.config.server.HOST;
} else {
return inetAddress;
}
}
public static boolean isLocalAddress(String inetAddress) {
return inetAddress.startsWith("127.");
}

View File

@@ -133,6 +133,23 @@ public class AbstractPlayerInteraction {
warpParty(id, portalId, mapid, mapid);
}
public void warpParty(int map, String portalName) {
int mapid = getMapId();
var warpMap = c.getChannelServer().getMapFactory().getMap(map);
var portal = warpMap.getPortal(portalName);
if (portal == null) {
portal = warpMap.getPortal(0);
}
var portalId = portal.getId();
warpParty(map, portalId, mapid, mapid);
}
public void warpParty(int id, int fromMinId, int fromMaxId) {
warpParty(id, 0, fromMinId, fromMaxId);
}

View File

@@ -448,6 +448,20 @@ public class Trade {
}
public static void inviteTrade(Character c1, Character c2) {
if ((c1.isGM() && !c2.isGM()) && c1.gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_TRADE) {
c1.message("You cannot trade with non-GM characters.");
log.info(String.format("GM %s blocked from trading with %s due to GM level.", c1.getName(), c2.getName()));
cancelTrade(c1, TradeResult.NO_RESPONSE);
return;
}
if ((!c1.isGM() && c2.isGM()) && c2.gmLevel() < YamlConfig.config.server.MINIMUM_GM_LEVEL_TO_TRADE) {
c1.message("You cannot trade with this GM character.");
cancelTrade(c1, TradeResult.NO_RESPONSE);
return;
}
if (InviteCoordinator.hasInvite(InviteType.TRADE, c1.getId())) {
if (hasTradeInviteBack(c1, c2)) {
c1.message("You are already managing this player's trade invitation.");

View File

@@ -53,6 +53,8 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author XoticStory
* @author Ronan
*/
// TODO: remove dependency on custom Npc.wz. All NPCs with id 9901910 and above are custom additions for player npcs.
// In summary: NPCs 9901910-9906599 and 9977777 are custom additions to HeavenMS that should be removed.
public class PlayerNPC extends AbstractMapObject {
private static final Logger log = LoggerFactory.getLogger(PlayerNPC.class);
private static final Map<Byte, List<Integer>> availablePlayerNpcScriptIds = new HashMap<>();
@@ -67,10 +69,6 @@ public class PlayerNPC extends AbstractMapObject {
private int dir, FH, RX0, RX1, CY;
private int worldRank, overallRank, worldJobRank, overallJobRank;
static {
getRunningMetadata();
}
public PlayerNPC(String name, int scriptId, int face, int hair, int gender, byte skin, Map<Short, Integer> equips, int dir, int FH, int RX0, int RX1, int CX, int CY, int oid) {
this.equips = equips;
this.scriptId = scriptId;
@@ -128,6 +126,12 @@ public class PlayerNPC extends AbstractMapObject {
}
}
public static void loadRunningRankData(Connection con, int worlds) throws SQLException {
getRunningOverallRanks(con);
getRunningWorldRanks(con, worlds);
getRunningWorldJobRanks(con);
}
public Map<Short, Integer> getEquips() {
return equips;
}
@@ -213,16 +217,6 @@ public class PlayerNPC extends AbstractMapObject {
client.sendPacket(PacketCreator.removePlayerNPC(this.getObjectId()));
}
private static void getRunningMetadata() {
try (Connection con = DatabaseConnection.getConnection()) {
getRunningOverallRanks(con);
getRunningWorldRanks(con);
getRunningWorldJobRanks(con);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void getRunningOverallRanks(Connection con) throws SQLException {
try (PreparedStatement ps = con.prepareStatement("SELECT max(overallrank) FROM playernpcs");
ResultSet rs = ps.executeQuery()) {
@@ -235,9 +229,8 @@ public class PlayerNPC extends AbstractMapObject {
}
}
private static void getRunningWorldRanks(Connection con) throws SQLException {
int numWorlds = Server.getInstance().getWorldsSize();
for (int i = 0; i < numWorlds; i++) {
private static void getRunningWorldRanks(Connection con, int worlds) throws SQLException {
for (int i = 0; i < worlds; i++) {
runningWorldRank.add(new AtomicInteger(1));
}
@@ -246,7 +239,7 @@ public class PlayerNPC extends AbstractMapObject {
while (rs.next()) {
int wid = rs.getInt(1);
if (wid < numWorlds) {
if (wid < worlds) {
runningWorldRank.get(wid).set(rs.getInt(2) + 1);
}
}
@@ -649,4 +642,4 @@ public class PlayerNPC extends AbstractMapObject {
e.printStackTrace();
}
}
}
}

View File

@@ -19,124 +19,17 @@
*/
package server.life;
import constants.id.ItemId;
import constants.id.MapId;
import constants.id.NpcId;
import net.server.Server;
import provider.Data;
import provider.DataProvider;
import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.WZFiles;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author RonanLana
*/
public class PlayerNPCFactory {
private static final DataProvider npcData = DataProviderFactory.getDataProvider(WZFiles.NPC);
private static final Map<Integer, List<PlayerNPC>> dnpcMaps = new HashMap<>();
private static Integer runningDeveloperOid = 2147483000; // 647 slots, long enough
public synchronized static boolean isExistentScriptid(int scriptid) {
return npcData.getData(scriptid + ".img") != null;
}
private static void loadDeveloperRoomMetadata(DataProvider npc) {
Data thisData = npc.getData(NpcId.CUSTOM_DEV + ".img");
if (thisData != null) {
DataProvider map = DataProviderFactory.getDataProvider(WZFiles.MAP);
thisData = map.getData("Map/Map7/" + MapId.DEVELOPERS_HQ + ".img");
if (thisData != null) {
DataProvider sound = DataProviderFactory.getDataProvider(WZFiles.SOUND);
thisData = sound.getData("Field.img");
if (thisData != null) {
Data md = thisData.getChildByPath("anthem/brazil");
if (md != null) {
Server.getInstance().setAvailableDeveloperRoom();
}
}
}
}
}
public synchronized static void loadFactoryMetadata() {
DataProvider npc = npcData;
loadDeveloperRoomMetadata(npc);
DataProvider etc = DataProviderFactory.getDataProvider(WZFiles.ETC);
Data dnpcData = etc.getData("DeveloperNpc.img");
if (dnpcData != null) {
for (Data data : dnpcData.getChildren()) {
int scriptId = Integer.parseInt(data.getName());
String name = DataTool.getString("name", data, "");
int face = DataTool.getIntConvert("face", data, 20000);
int hair = DataTool.getIntConvert("hair", data, 30000);
int gender = DataTool.getIntConvert("gender", data, 0);
byte skin = (byte) DataTool.getIntConvert("skin", data, 0);
int dir = DataTool.getIntConvert("dir", data, 0);
int mapid = DataTool.getIntConvert("map", data, 0);
int FH = DataTool.getIntConvert("fh", data, 0);
int RX0 = DataTool.getIntConvert("rx0", data, 0);
int RX1 = DataTool.getIntConvert("rx1", data, 0);
int CX = DataTool.getIntConvert("cx", data, 0);
int CY = DataTool.getIntConvert("cy", data, 0);
Map<Short, Integer> equips = new HashMap<>();
for (Data edata : data.getChildByPath("equips").getChildren()) {
short equippos = (short) DataTool.getIntConvert("pos", edata);
int equipid = DataTool.getIntConvert("itemid", edata);
equips.put(equippos, equipid);
}
List<PlayerNPC> dnpcSet = dnpcMaps.get(mapid);
if (dnpcSet == null) {
dnpcSet = new LinkedList<>();
dnpcMaps.put(mapid, dnpcSet);
}
dnpcSet.add(new PlayerNPC(name, scriptId, face, hair, gender, skin, equips, dir, FH, RX0, RX1, CX, CY, runningDeveloperOid));
runningDeveloperOid++;
}
} else {
Data thisData = npc.getData(NpcId.CUSTOM_DEV + ".img");
if (thisData != null) {
byte[] encData = {0x52, 0x6F, 0x6E, 0x61, 0x6E};
String name = new String(encData);
int face = 20104, hair = 30215, gender = 0, skin = 0, dir = 0, mapid = MapId.DEVELOPERS_HQ;
int FH = 4, RX0 = -143, RX1 = -243, CX = -193, CY = 117, scriptId = NpcId.CUSTOM_DEV;
Map<Short, Integer> equips = new HashMap<>();
equips.put((short) -1, ItemId.GREEN_HEADBAND);
equips.put((short) -11, ItemId.TIMELESS_NIBLEHEIM);
equips.put((short) -8, ItemId.BLUE_KORBEN);
equips.put((short) -6, ItemId.MITHRIL_PLATINE_PANTS);
equips.put((short) -7, ItemId.BLUE_CARZEN_BOOTS);
equips.put((short) -5, ItemId.MITHRIL_PLATINE);
List<PlayerNPC> dnpcSet = dnpcMaps.get(mapid);
if (dnpcSet == null) {
dnpcSet = new LinkedList<>();
dnpcMaps.put(mapid, dnpcSet);
}
dnpcSet.add(new PlayerNPC(name, scriptId, face, hair, gender, (byte) skin, equips, dir, FH, RX0, RX1, CX, CY, runningDeveloperOid));
runningDeveloperOid++;
}
}
}
public synchronized static List<PlayerNPC> getDeveloperNpcsFromMapid(int mapid) {
return dnpcMaps.get(mapid);
}
}

View File

@@ -28,7 +28,10 @@ import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.WZFiles;
import scripting.event.EventInstanceManager;
import server.life.*;
import server.life.AbstractLoadedLife;
import server.life.LifeFactory;
import server.life.Monster;
import server.life.PlayerNPC;
import server.partyquest.GuardianSpawnPoint;
import tools.DatabaseConnection;
import tools.StringUtil;
@@ -249,13 +252,6 @@ public class MapFactory {
} catch (SQLException e) {
e.printStackTrace();
}
List<PlayerNPC> dnpcs = PlayerNPCFactory.getDeveloperNpcsFromMapid(mapid);
if (dnpcs != null) {
for (PlayerNPC dnpc : dnpcs) {
map.addPlayerNPCMapObject(dnpc);
}
}
}
loadLifeFromWz(map, mapData);